小言_互联网的博客

力扣刷题:数组篇

378人阅读  评论(0)


1. 两数之和

题目介绍

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 103
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

最容易想到的方法是枚举数组中的每一个数 x,寻找数组中是否存在 target - x。当我们使用遍历整个数组的方式寻找 target - x 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x

class Solution {
   
    public int[] twoSum(int[] nums, int target) {
   
        for (int i = 0; i < nums.length; i++) {
   
            for (int j = i + 1; j < nums.length; j++) {
   
                if (nums[i] + nums[j] == target) {
   
                    return new int[]{
   i, j};
                }
            }
        }
        return new int[0];
    }
}

注意到上一个解法的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。

因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。

使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)

我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。

class Solution {
   
    public int[] twoSum(int[] nums, int target) {
   
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
   
            if (hashMap.containsKey(target - nums[i])) {
   
                return new int[]{
   hashMap.get(target - nums[i]), i};
            }
            hashMap.put(nums[i], i);
        }
        return new int[0];
    }
}

164. 最大间距

题目介绍

给定一个无序的数组,找出数组在排序之后,相邻元素之间最大的差值。

如果数组元素个数小于 2,则返回 0。

示例 1:

输入: [3,6,9,1]
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。

示例 2:

输入: [10]
输出: 0
解释: 数组元素个数小于 2,因此返回 0。

说明:

  • 你可以假设数组中所有元素都是非负整数,且数值在 32 位有符号整数范围内。
  • 请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-gap
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

我们先对传入的数组进行临界检查,然后对数组进行排序,排序直接调用库函数就行了。

然后从左向右依次两两比较,找出最大的那个间距,然后重新赋值给 maxSpace 就行了。

class Solution {
   
    public int maximumGap(int[] nums) {
   
        if (nums == null || nums.length < 2) return 0;
        Arrays.sort(nums);
        int maxSpace = 0;
        for (int i = 1; i < nums.length; i++) {
   
            maxSpace = Math.max(maxSpace, nums[i] - nums[i - 1]);
        }
        return maxSpace;
    }
}

240. 搜索二维矩阵 II

题目介绍

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target

该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

示例 1:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true

示例 2:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= n, m <= 300
  • -109 <= matix[i][j] <= 109
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列
  • -109 <= target <= 109

相同题目:

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-a-2d-matrix-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

如果 target = 5,我们来看看在不使用暴力搜索的情况下,如何快速查找。

从右上角开始查找,15>5,因此,我们需要向左移动。为什么不向下移动,因为,向下的数都比15大。

向左移动完毕之后,11>5,因此,我们需要向左移动。为什么不向下移动,因为,向下的数都比11大。

向左移动完毕之后,7>5,因此,我们需要向左移动。为什么不向下移动,因为,向下的数都比7大。

向左移动完毕之后,4<5,因此,我们需要向下移动。为什么不向左移动,因为,向左的数都比4小。

向下移动完毕之后,5=5,因此,我们需要返回true。

可能上面的步骤不是很难理解,但有人可能会想,为啥不从左上角的 1 开始搜索呢?原因很简单,左上角的 1 下边都是比 1 大的,左上角的 1 右边都是比 1 大的,两个都是大的,你咋知道该下还是垓右,同理,右下角的 30 也类似,向左看都比 30 小,向上看都比 30 小。两个都是小的,你咋知道该左还是垓上,因此,只有从左下角的18处或者右上角的15处开始搜索才是正解。因为当target大于、小于、等于当前数组元素值时,都有方向可以走,没有歧义。

class Solution {
   
    public boolean searchMatrix(int[][] matrix, int target) {
   
        // 临界检查
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
        // 获取行数
        int rows = matrix.length;
        // 获取列数
        int cols = matrix[0].length;
        // 定义行指针
        int row = 0;
        // 定义列指针(从右上角开始搜索)
        int col = cols - 1;
        // 循环搜索
        while ((row >= 0 && row < rows) && (col >= 0 && col < cols)) {
   
            if (target > matrix[row][col]) {
   
                row++;
            } else if (target < matrix[row][col]) {
   
                col--;
            } else {
   
                return true;
            }
        }
        // 没有找到
        return false;
    }
}

26. 删除有序数组中的重复项

题目介绍

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
   
    print(nums[i]);
}

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 ,并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:

  • 0 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按升序排列

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

定义两个指针,一个指针用于从左向右依次扫描,我们称为fast快指针,另一个指针用于存储不重复的数字,由于移动较慢,我们称为slow慢指针。在定义之初,slow应该从0开始,而fast却不必从0开始,自己和自己比较这是不明智的,如下图:

开始第一轮比较,快指针所指向的元素和慢指针所指向的元素相等,则快指针直接后移即可。

开始再一次比较,快指针所指向的元素和慢指针所指向的元素不等,则慢指针加加,然后把快指针所指向的元素放入慢指针所指向的位置,快指针直接后移即可。

按照上边这种模式一直向后扫描,直到快指针全部扫描完,最终的结果也就出来了,但是我们返回的时候,由于慢指针是下标,并不代表新数组的长度,因此需要加一再返回。

class Solution {
   
    public int removeDuplicates(int[] nums) {
   
        if (nums.length == 0) return 0;
        int slow = 0;
        for (int fast = 1; fast < nums.length; fast++) {
   
            if (nums[fast] != nums[slow]) {
   
                nums[++slow] = nums[fast];
            }
        }
        return slow + 1;
    }
}

27. 移除元素

题目介绍

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
   
    print(nums[i]);
}

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-element
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

定义两个指针,一个指针用于从左向右依次扫描,我们称为fast快指针,另一个指针用于存储和 val 不相同的数字,由于移动较慢,我们称为slow慢指针。在定义之初,slow应该从0开始,而fast也必须从0开始,如下图:

如果 val = 2,开始进行第一轮循环,快指针所指向的元素和val不相等,则应该把快指针对应的元素拷贝到慢指针所对应的位置处,然后快指针加加、慢指针加加。

同理,快指针所指向的元素和val不相等,则应该把快指针对应的元素拷贝到慢指针所对应的位置处,然后快指针加加、慢指针加加。

此时,快指针所指向的元素和val相等,则快指针直接加加即可。

此时,快指针所指向的元素和val相等,则快指针直接加加即可。

现在,快指针所指向的元素和val不相等,则应该把快指针对应的元素拷贝到慢指针所对应的位置处,然后快指针加加、慢指针加加。

按照上边这种模式,快指针一直向后扫描,直到全部扫描完毕,最终结果就出来了,慢指针所对应的就是新数组的大小。

class Solution {
   
    public int removeElement(int[] nums, int val) {
   
        if (nums.length == 0) return 0;
        int slow = 0;
        for (int fast = 0; fast < nums.length; fast++) {
   
            if (nums[fast] != val) {
   
                nums[slow++] = nums[fast];
            }
        }
        return slow;
    }
}

75. 颜色分类

题目介绍

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用 整数 012 分别表示红色、白色和蓝色。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]

示例 3:

输入:nums = [0]
输出:[0]

示例 4:

输入:nums = [1]
输出:[1]

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i] 为 0、1 或 2

进阶:

  • 你可以不使用代码库中的排序函数来解决这道题吗?
  • 你能想出一个仅使用常数空间的一趟扫描算法吗?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-colors
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

解决这道题,扫描一遍,只需要把0放到数组的左边,把2放到数组的右边就行了。因此,我们至少需要定义两个指针,一个指针用来存放0,另一个指针用来存放2,除了这两个指针,我们还需要一个指针从左到右进行扫描,因此是三指针。

从左向右依次扫描,当遇到元素为2的时候,交换黑色和蓝色指针所指向的值,然后蓝色指针减减。

但是在这里要注意,有可能蓝色指针指向的位置也是一个2,这样交换了位置之后,我们还需要再进行一次判断。

交换前(换了一个图):

交换后(上图交换后):

我们针对上图的情况,需要进行再次判断,如果此时黑色指针指向的还是2,那就让他和蓝色指针所指向的值交换,然后蓝色指针减减、黑色指针加加。

如果扫描时遇到的是0,则让黑色指针所指向的值和绿色指针所指向的值进行交换,然后绿色指针加加、黑色指针加加。

此时黑色指针扫描遇到的仍旧是2,则与蓝色指针所指向的值进行交换,然后蓝色指针减减,再然后进行二次判断。

再进行二次判断的时候,发现黑色指针所指向的值是1,1既不用放到数组左边也不用放到数组右边,所以直接跳过,即黑色指针加加。

此时我们发现数组中元素已然有序,则结束条件就是黑色指针 大于 蓝色指针的时候退出循环。

class Solution {
   
    public void sortColors(int[] nums) {
   
        int i = 0;                  //扫描
        int l = 0;                  //放零
        int r = nums.length - 1;    //放二

        while (i <= r) {
   
            if (nums[i] == 0) {
   
                swap(nums, l++, i++);
            } else if (nums[i] == 1) {
   
                i++;
            } else {
   
                swap(nums, r--, i);
            }
        }
    }

    /**
     * 交换数组中元素
     *
     * @param nums 数组
     * @param i1   索引1
     * @param i2   索引2
     */
    public void swap(int[] nums, int i1, int i2) {
   
        int tmp = nums[i1];
        nums[i1] = nums[i2];
        nums[i2] = tmp;
    }
}

977. 有序数组的平方

题目介绍

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
     排序后,数组变为 [0,1,9,16,100]

示例 2:

输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums 已按 非递减顺序 排序

进阶:

  • 请你设计时间复杂度为 O(n) 的算法解决本问题。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/squares-of-a-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

解决这道题的思路主要是定义两个指针,分别指向平方后的第一个元素和最后一个元素,然后再定义一个新数组。

判断这两个指针所指向的元素哪一个大,把大的值存放到新数组中,然后指向大的元素一方的指针移动,新数组的指针减减即可。

class Solution {
   
    public int[] sortedSquares(int[] nums) {
   
        // 创建新的数组
        int[] tmps = new int[nums.length];
        // 定义三个指针
        int i = nums.length - 1;
        int l = 0;
        int r = nums.length - 1;
        while (l <= r) {
   
            if (nums[l] * nums[l] >= nums[r] * nums[r]) {
   
                tmps[i--] = nums[l] * nums[l++];
            } else {
   
                tmps[i--] = nums[r] * nums[r--];
            }
        }
        // 返回最终结果
        return tmps;
    }
}

剑指 Offer 03. 数组中重复的数字

题目介绍

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

限制:

  • 2 <= n <= 100000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

我们可以利用 set 集合元素不能重复这一特点,循环遍历数组元素,依次加入到 set 集合中,如果加入失败,就代表该数字重复。

class Solution {
   
    public int findRepeatNumber(int[] nums) {
   
        HashSet<Integer> hashSet = new HashSet<>();
        for (int num : nums) {
   
            if (!hashSet.add(num)) {
   
                return num;
            }
        }
        return -1;
    }
}

10.01. 合并排序的数组

题目介绍

给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。

初始化 A 和 B 的元素数量分别为 m 和 n。

示例:

输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]

说明:

  • A.length == n + m

相同题目:

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sorted-merge-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

定义三个指针,一个指针指向A数组有效数据的最后一个元素,一个指针指向B数组的最后一个元素,还有一个指针指向A数组的最后一个元素。具体实现思路就是,比较绿色指针和橙色指针所对应的数据的大小,拿出较大的值放到黑色指针所指的地方,然后指针减减。

在这个过程中,橙色指针的结束条件就是 bi >= 0,绿色指针也应该满足 ai >= 0,最终实现的代码如下:

class Solution {
   
    public void merge(int[] A, int m, int[] B, int n) {
   
        int ai = m - 1;
        int bi = n - 1;
        int cur = A.length - 1;

        while (bi >= 0) {
   
            if (ai >= 0 && A[ai] > B[bi]) {
   
                A[cur--] = A[ai--];
            } else {
   
                A[cur--] = B[bi--];
            }
        }
    }
}

16.16. 部分排序

题目介绍

给定一个整数数组,编写一个函数,找出索引m和n,只要将索引区间[m,n]的元素排好序,整个数组就是有序的。注意:n-m尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n],若不存在这样的m和n(例如整个数组是有序的),请返回[-1,-1]。

示例:

输入: [1,2,4,7,10,11,7,12,6,7,16,18,19]
输出: [3,9]

提示:

  • 0 <= len(array) <= 1000000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sub-sort-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目实现

我们首先从左向右依次扫描,遇到比 max 还大的值就直接记录下来,说明从左向右是升序,如果一旦遇到比 max 的值小的,说明这个位置是需要排序的位置,按照这个模式从左向右扫描完一遍,就能找到区间 n 的值。

我们然后从右向左依次扫描,遇到比 min 还小的值就直接记录下来,说明从右向左是降序,如果一旦遇到比 min 的值大的,说明这个位置是需要排序的位置,按照这个模式从右向左扫描完一遍,就能找到区间 m 的值。

除了上边的解题思路,还需要注意,数组的长度可能为0,因此,我们需要对数组的长度进行判断。

class Solution {
   
    public int[] subSort(int[] array) {
   
        if (array.length == 0) return new int[]{
   -1, -1};

        // 从左向右扫描逆序对的右下标(正序,从小到大)
        int max = array[0];
        int r = -1;
        for (int i = 1; i < array.length; i++) {
   
            if (array[i] >= max) {
   
                max = array[i];
            } else {
   
                r = i;
            }
        }

        // 如果从左到右扫描发现没有找到需要排序的位置则返回
        if (r == -1) return new int[]{
   -1, -1};

        // 从右向左扫描逆序对的左下标(逆序,从大到小)
        int min = array[array.length - 1];
        int l = -1;
        for (int i = array.length - 2; i >= 0; i--) {
   
            if (array[i] <= min) {
   
                min = array[i];
            } else {
   
                l = i;
            }
        }

        return new int[]{
   l, r};
    }
}

转载:https://blog.csdn.net/qq_38490457/article/details/117378145
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场