LeetCode精选题之数组和矩阵

LeetCode精选题之数组和矩阵

参考资料:CyC2018的LeetCode题解

1 移动零–LeetCode283

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

  • 必须在原数组上操作,不能拷贝额外的数组。
  • 尽量减少操作次数。

思路一:冒泡排序的思路。

class Solution {
    public void moveZeroes(int[] nums) {
        if (nums == null || nums.length == 0) {
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) {
                continue;
            }
            int copy = nums[i];
            int j = i;
            for (; j > 0 && nums[j-1] == 0; j--) {
                nums[j] = nums[j-1];
            }
            nums[j] = copy;
        }
    }
}

思路二:双指针。设置一个指针k,它的含义是非零元素待插入的位置。在遍历的过程,遇到非零元素就和k位置的元素进行交换,同时指针k后移。

class Solution {
    public void moveZeroes(int[] nums) {
        if (nums == null || nums.length == 0) {
            return ;
        }
        int k = 0;// nums中,[0, k)的元素为非0元素
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                // 防止特殊用例(没有非零元素)的每一个元素与自身交换
                if (i != k) {
                    swap(nums, i, k);
                }
                k++;
            }
        }
        return ;
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

2 重塑矩阵–LeetCode566

在MATLAB中,有一个非常有用的函数 reshape,它可以将一个矩阵重塑为另一个大小不同的新矩阵,但保留其原始数据。给出一个由二维数组表示的矩阵,以及两个正整数r和c,分别表示想要的重构的矩阵的行数和列数。重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。如果具有给定参数的reshape操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

示例 1:

输入: 
nums = 
[[1,2],
 [3,4]]
r = 1, c = 4
输出: 
[[1,2,3,4]]
解释:
行遍历nums的结果是 [1,2,3,4]。新的矩阵是 1 * 4 矩阵, 用之前的元素值一行一行填充新矩阵。

示例 2:

输入: 
nums = 
[[1,2],
 [3,4]]
r = 2, c = 4
输出: 
[[1,2],
 [3,4]]
解释:
没有办法将 2 * 2 矩阵转化为 2 * 4 矩阵。 所以输出原矩阵。

注意:

  1. 给定矩阵的宽和高范围在 [1, 100]。
  2. 给定的 r 和 c 都是正数。
class Solution {
    public int[][] matrixReshape(int[][] nums, int r, int c) {
        int row = nums.length;
        int col = nums[0].length;
        if (row * col != r * c) {
            return nums;
        }
        int[][] reshapeNums = new int[r][c];
        int index = 0;
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                reshapeNums[i][j] = nums[index / col][index % col];
                index++;
            }
        }
        return reshapeNums;
    }
}

3 最大连续1的个数–LeetCode485

给定一个二进制数组, 计算其中最大连续1的个数。

示例 1:

输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.

注意:

  • 输入的数组只包含 0 和1。
  • 输入数组的长度是正整数,且不超过 10,000。
class Solution {
    public int findMaxConsecutiveOnes(int[] nums) {
        int max = 0, cnt = 0;
        for (int x : nums) {
            cnt = x == 0 ? 0 : cnt + 1;
            max = Math.max(max, cnt);
        }
        return max;
    }
}

4 搜索二维矩阵 II–LeetCode240(Medium)

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

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

示例:

现有矩阵 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。
给定 target = 20,返回 false。

思路:从左下角开始二分查找。(注:从右上角查找也是一样的思路)

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length;
        if (matrix == null || row == 0) {
            return false;
        }
        int col = matrix[0].length;
        int i = row-1, j = 0;
        while (i >= 0 && j < col) {
            if (matrix[i][j] > target) {
                i--;
            }else if (matrix[i][j] < target) {
                j++;
            }else {
                return true;
            }
        }
        return false;
    }
}

5 有序矩阵中第K小的元素–LeetCode378(Medium)

给定一个 n x n矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。

示例:

matrix = [
   [ 1,  5,  9],
   [10, 11, 13],
   [12, 13, 15]
],
k = 8,

返回 13。

提示:你可以假设 k 的值永远是有效的,1 ≤ k ≤ n2

思路(参考资料1):

  1. 找出二维矩阵中最小的数left,最大的数right,那么第k小的数必定在left~right之间
  2. mid=(left+right) / 2;在二维矩阵中寻找小于等于mid的元素个数count
  3. 若这个count小于k,表明第k小的数在右半部分且不包含mid,即left=mid+1, right=right,又保证了第k小的数在left~right之间
  4. 若这个count大于k,表明第k小的数在左半部分且可能包含mid,即left=left, right=mid,又保证了第k小的数在left~right之间
  5. 因为每次循环中都保证了第k小的数在left~right之间,当left==right时,第k小的数即被找出,等于right

注意:这里的left mid right是数值,不是索引位置。

举例(参考资料3):

[1  2
12 100]
k = 3

那么刚开始 left = 1, right = 100, mid = 50, 遍历完 cnt = 3,此时 right 更新为 50
此时 left = 1, right = 50, mid = 25, 遍历完之后 cnt = 3, 此时 right 更新为 25
此时 left = 1, right = 25, mid = 13, 遍历完之后 cnt = 3, 此时 right 更新为 13
此时 left = 1, right = 13, mid = 7, 遍历完之后 cnt = 2, 此时 left 更新为8
此时 left = 8, right = 13, mid = 10, 遍历完之后 cnt = 2, 此时 left 更新为 11
此时 left = 11, right = 12, mid = 11, 遍历完之后 cnt = 2, 此时 left 更新为 12
循环结束,left 和 right 均为 12,任意返回一个即可。

参考资料:
1、jacksu1024的题解
2、LeetCode官方题解的解法三
3、LeetCode 378. Kth Smallest Element in a Sorted Matrix 有序矩阵中第K小的元素

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int n  = matrix.length;
        // 这里的left right mid 表示的是元素,不是索引
        int left = matrix[0][0];
        int right = matrix[n-1][n-1];
        while (left < right) {
            int mid = left + (right - left) / 2;
            // 整个数组中小于等于mid的元素个数
            int count = calCountSmallerThanMid(matrix, mid, n);
            if (count < k) {
                left = mid + 1;
            }else {
                right = mid;
            }
        }
        return right;
    }

    // 计算整个数组中小于等于mid的元素个数
    // 查找方式是按列查找,初始位置在二维矩阵的左下角
    private int calCountSmallerThanMid(int[][] matrix, int mid, int n) {
        int i = n-1;
        int j = 0;
        int cnt = 0;
        while (i >= 0 && j < n) {
            if (matrix[i][j] <= mid) {
                cnt += i + 1;
                j++;
            }else {
                i--;
            }
        }
        return cnt;
    }
}

6 错误的集合–LeetCode645

集合 S 包含从1到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个元素复制成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复。

给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。

示例 1:

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

注意:

  • 给定数组的长度范围是 [2, 10000]。
  • 给定的数组是无序的。
class Solution {
    public int[] findErrorNums(int[] nums) {
        Set<Integer> set = new HashSet<>();
        int[] res = new int[2];
        int curSum = 0;
        int len = nums.length;
        for (int num : nums) {
            if (set.contains(num)) {
                res[0] = num;
            }else {
                set.add(num);
            }
            curSum += num;
        }
        res[1] = (1 + len)*len/2 - curSum + res[0];
        return res;
    }
}

改进:不用额外的空间。
对于寻找重复的元素:

  • 通过将nums[Math.abs(nums[i]) - 1]的元素置负:
  • 当某个nums[Math.abs(nums[i]) - 1]已经被置负时, Math.abs(nums[i])即为重复元素!

例如:

[4, 1, 3, 3] 
-->  [4, 1, 3, -3] 
--> [-4, 1, 3, -3] 
--> [-4, 1, -3, -3] 
--> 此时n == 3, 而nums[2]已经被置负, 所以3为重复元素

参考资料:Jasonkay

class Solution {
    public int[] findErrorNums(int[] nums) {
        int[] res = new int[2];
        int len = nums.length;
        int sum = 0;
        for (int i = 0; i < len; i++) {
            int position = Math.abs(nums[i]) - 1;
            if (nums[position] > 0) {
                nums[position] = -nums[position];
            } else {
                res[0] = Math.abs(nums[i]);
            }
            sum += Math.abs(nums[i]);
        }
        res[1] = (1 + len)*len/2 - sum + res[0];
        return res;
    }
}

还有一种思路:通过交换数组元素,使得数组上的元素在正确的位置上。

public int[] findErrorNums(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {
            swap(nums, i, nums[i] - 1);
        }
    }
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != i + 1) {
            return new int[]{nums[i], i + 1};
        }
    }
    return null;
}

private void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

7 寻找重复数–LeetCode287(Medium)

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2

示例 2:

输入: [3,1,3,4,2]
输出: 3

说明:

  1. 不能更改原数组(假设数组是只读的)。
  2. 只能使用额外的 O(1) 的空间。
  3. 时间复杂度小于 O(n2) 。
  4. 数组中只有一个重复的数字,但它可能不止重复出现一次。

思路:二分查找。这里的二分法用于确定一个有范围的整数。

例如:区间 [1,7]的中位数是 4,遍历整个数组,统计小于等于 4 的整数的个数,如果不存在重复元素,最多为 4 个。等于 4 的时候区间 [1,4]内也可能有重复元素。但是,如果整个数组里小于等于 4 的整数的个数严格大于 4 的时候,就可以说明重复的数存在于区间 [1,4]

参考题解:liweiwei1419

class Solution {
    public int findDuplicate(int[] nums) {
        int len = nums.length;
        int left = 1;
        int right = len - 1;
        while (left < right) {
            int mid = (left + right) >>> 1;
            int cnt = 0;
            // nums中小于等于mid的个数
            for (int num : nums) {
                if (num <= mid) {
                    cnt++;
                }
            }
            // 根据抽屉原理,举例:小于等于 4 的个数如果严格大于 4 个
            // 此时重复元素一定出现在 [1, 4] 区间里
            if (cnt > mid) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

8 优美的排列 II–LeetCode667(Medium)

给定两个整数 n 和 k,你需要实现一个数组,这个数组包含从 1 到 n 的 n 个不同整数,同时满足以下条件:

① 如果这个数组是 [a1, a2, a3, … , an] ,那么数组 [|a1 - a2|, |a2 - a3|, |a3 - a4|, … , |an-1 - an|] 中应该有且仅有 k 个不同整数;.

② 如果存在多种答案,你只需实现并返回其中任意一种.

示例 1:

输入: n = 3, k = 1
输出: [1, 2, 3]
解释: [1, 2, 3] 包含 3 个范围在 1-3 的不同整数, 并且 [1, 1] 中有且仅有 1 个不同整数 : 1

示例 2:

输入: n = 3, k = 2
输出: [1, 3, 2]
解释: [1, 3, 2] 包含 3 个范围在 1-3 的不同整数, 并且 [2, 1] 中有且仅有 2 个不同整数: 1 和 2

提示:n 和 k 满足条件 1 <= k < n <= 104.

若n=8,初始状态为:

1 2 3 4 5 6 7 8
k=1         | 1 2 3 4 5 6 7 8 (不翻转,直接返回)
k=2         1 | 8 7 6 5 4 3 2
k=3         1 8 | 2 3 4 5 6 7
k=4         1 8 2 | 7 6 5 4 3

总结规律:当k > 1时,需要翻转的次数为k-1次,每次翻转保留前m(m = 1,2,3...k-1)个数,每次翻转都在原数组进行。

参考题解:wzliang

class Solution {
    public int[] constructArray(int n, int k) {
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            res[i] = i + 1;
        }
        if (k == 1) {
            return res;
        }
        // 翻转k-1次
        for (int m = 1; m < k; m++) {
            reverse(res, m, n - 1);
        }
        return res;
    }

    private void reverse(int[] arr, int i, int j) {
        while (i < j) {
            int copy = arr[i];
            arr[i] = arr[j];
            arr[j] = copy;
            i++;
            j--;
        }
    }
}

9 数组的度–LeetCode697

给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。

你的任务是找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例 1:

输入: [1, 2, 2, 3, 1]
输出: 2
解释: 
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.

示例 2:

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

注意:

  • nums.length在1到50,000区间范围内。
  • nums[i]是一个在0到49,999范围内的整数。

思路:

  • 具有度数 d 的数组必须有一些元素 x 出现 d 次。如果某些子数组具有相同的度数,那么某些元素 x (出现 d 次)。最短的子数组是将从 x 的第一次出现到最后一次出现的数组。
  • 对于给定数组中的每个元素,让我们知道 left是它第一次出现的索引; right是它最后一次出现的索引。例如,当 nums = [1,2,3,2,5]时,left[2] = 1right[2] = 3
  • 然后,对于出现次数最多的每个元素 x,right[x] - left[x] + 1将是我们的候选答案,我们将取这些候选的最小值。

思路来源:LeetCode官方题解。

class Solution {
    public int findShortestSubArray(int[] nums) {
        Map<Integer, Integer> count = new HashMap<>();
        Map<Integer, Integer> left = new HashMap<>();
        Map<Integer, Integer> right = new HashMap<>();

        for (int i = 0; i < nums.length; i++) {
            int num = nums[i];
            if (left.get(num) == null) {
                left.put(num, i);
            }
            right.put(num, i);
            count.put(num, count.getOrDefault(num, 0) + 1);
        }

        int degree = Collections.max(count.values());
        int res = nums.length;
        for (int key : count.keySet()) {
            if (count.get(key) == degree) {
                res = Math.min(res, right.get(key) - left.get(key) + 1);
            }
        }
        return res;
    }
}

10 托普利茨矩阵–LeetCode766

如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是 托普利茨矩阵 。

给定一个 M x N 的矩阵,当且仅当它是托普利茨矩阵时返回 True。

示例 1:

输入: 
matrix = [
  [1,2,3,4],
  [5,1,2,3],
  [9,5,1,2]
]
输出: True
解释:
在上述矩阵中, 其对角线为:
"[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]"。
各条对角线上的所有元素均相同, 因此答案是True。

示例 2:

输入:
matrix = [
  [1,2],
  [2,2]
]
输出: False
解释: 
对角线"[1, 2]"上的元素不同。

说明:

  1. matrix 是一个包含整数的二维数组。
  2. matrix 的行数和列数均在 [1, 20]范围内。
  3. matrix[i][j]包含的整数在 [0, 99]范围内。

思路一:以第一行和第一列上的元素分别为起点,判断对角线上的元素是否都相等。

class Solution {
    public boolean isToeplitzMatrix(int[][] matrix) {
        // 第一列
        for (int i = 0; i < matrix.length; i++) {
            if (!isSame(matrix, i, 0)) {
                return false;
            }
        }
        // 第一行
        for (int j = 0; j < matrix[0].length; j++) {
            if (!isSame(matrix, 0, j)) {
                return false;
            }
        }
        return true;
    }

    private boolean isSame(int[][] matrix, int row, int col) {
        int start = matrix[row][col];
        while (row < matrix.length && col < matrix[0].length) {
            if (matrix[row][col] != start) {
                return false;
            }
            row++;
            col++;
        }
        return true;
    }
}

思路二:直接判断每个位置(第二行第二列开始)和左上角的元素是否相等即可。

class Solution {
    public boolean isToeplitzMatrix(int[][] matrix) {
        int rows = matrix.length;
        int cols = matrix[0].length;
        // 一行或一列的情况满足题目要求
        if (rows == 1 || cols == 1) {
            return true;
        }
        // 第二行第二列开始,每个位置与左上角的元素比较是否相等
        for (int i = 1; i < rows; i++) {
            for (int j = 1; j < cols; j++) {
                if (matrix[i][j] != matrix[i-1][j-1]) {
                    return false;
                }
            }
        }
        return true;
    }
}

11 数组嵌套–LeetCode565(Medium)

索引从0开始长度为N的数组A,包含0到N - 1的所有整数。找到最大的集合S并返回其大小,其中 S[i] = {A[i], A[A[i]], A[A[A[i]]], ... }且遵守以下的规则。

假设选择索引为i的元素A[i]S的第一个元素,S的下一个元素应该是A[A[i]],之后是A[A[A[i]]]… 以此类推,不断添加直到S出现重复的元素。

示例 1:

输入: A = [5,4,0,3,1,6,2]
输出: 4
解释: 
A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.

其中一种最长的 S[K]:
S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}

提示:

  1. [1, 20,000]之间的整数。
  2. A中不含有重复的元素。
  3. A中的元素大小在[0, N-1]之间。
class Solution {
    public int arrayNesting(int[] nums) {
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            // 如果没有被访问过
            if (nums[i] != Integer.MAX_VALUE) {
                int start = nums[i]; // start指的是当前考虑的值
                int cnt = 0;
                // start的下一个位置没有被访问过
                while (nums[start] != Integer.MAX_VALUE) {
                    int temp = start;
                    start = nums[start];
                    nums[temp] = Integer.MAX_VALUE;
                    cnt++;
                }
                res = Math.max(res, cnt);
            }
        }
        return res;
    }
}

12 最多能完成排序的块–LeetCode769(Medium)

数组arr[0, 1, ..., arr.length - 1]的一种排列,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。

我们最多能将数组分成多少块?

示例 1:

输入: arr = [4,3,2,1,0]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。

示例 2:

输入: arr = [1,0,2,3,4]
输出: 4
解释:
我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。

注意:

  • arr的长度在[1, 10]之间。
  • arr[i][0, 1, ..., arr.length - 1]的一种排列。

思路:不断更新当下区间的最右端,当遍历的下标与最右端重合的时候,说明这个区间已经自洽了。

class Solution {
    public int maxChunksToSorted(int[] arr) {
        int res = 0;
        int right = arr[0];
        for (int i = 0; i < arr.length; i++) {
            right = Math.max(right, arr[i]);
            if (right == i) {
                res++;
            }
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值