leetcode_数组与矩阵

文章目录

    • 283. 移动零
    • 566. 重塑矩阵
    • 485. 最大连续1的个数
    • 240. 搜索二维矩阵 II
    • 378. 有序矩阵中第K小的元素
    • 645. 错误的集合
    • 667. 优美的排列 II
    • 697. 数组的度
    • 766. 托普利茨矩阵
    • 565. 数组嵌套
    • 769. 最多能完成排序的块

283. 移动零

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

示例:

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

说明:

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

题解:
1. 冒泡思想
每一趟扫描遇到0就将其与后一个数交换,直到该0到末尾的位置。

class Solution {
    public void moveZeroes(int[] nums) {
        
        int len = nums.length;
        for (int i = len; i>0; i--){
            int flag = 0;
            for (int j = 0; j<len-1; j++){
                if (nums[j] == 0){
                    swap(nums, j, j+1);
                    flag = 1;
                }
            }
            if (flag == 0){   //上一轮没有进行交换,则提前终止
                return;
            }
        }
    }
    
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

时间复杂度:O(n^2)
空间复杂度:O(1)

  1. 两次遍历
  • 第一遍:遇到非0元素就往前移
  • 第二遍:末尾剩下的区域填0
class Solution {
    public void moveZeroes(int[] nums) {
        int j = 0;
        for(int i = 0; i<nums.length; i++){
            if (nums[i] != 0){
                nums[j++] = nums[i];
            }
        }
        for (int i = j; i<nums.length; i++){
            nums[i] = 0;
        }
        
    }
}

时间复杂度:O(n)
空间复杂度:O(1)

  1. 快排思想
    以0为基准点,不等于0的放左边,等于0的放右边
    注:可能有负数
class Solution {
    public void moveZeroes(int[] nums) {
        int j = -1;
        for(int i = 0; i<nums.length; i++){
            if (nums[i]!=0){
                swap(nums, ++j, i);
            }
        }
        
    }
        public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)


566. 重塑矩阵

在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, 100]。
给定的 r 和 c 都是正数。

题解:
将二维数组看作一维数组。将一维数组下标对应成二维数组下标。

class Solution {
    public int[][] matrixReshape(int[][] nums, int r, int c) {
        int n = nums.length;
        int m = 0;
        if (n!=0){
            m = nums[0].length;
        }
        if (n*m != r*c || n == 0){
            return nums;
        }
        
        int[][] res = new int[r][c];
        for (int i = 0; i<n*m; i++){
            res[i/c][i%c] = nums[i/m][i%m];
        }
        return res;
    }
}

时间复杂度:O(nm)
空间复杂度:O(n
m)


485. 最大连续1的个数

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

示例 1:

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

注意:

输入的数组只包含 0 和1。
输入数组的长度是正整数,且不超过 10,000。

题解:
count记录当前连续1的个数,遇到1就自增1,遇到0则归0;

class Solution {
    public int findMaxConsecutiveOnes(int[] nums) {
        int res = 0;
        int count = 0;
        for (int i = 0; i<nums.length; i++){
            if (nums[i] == 1){
                count++;
            }
            else{
                count = 0;
            }
            res = Math.max(res, count);
        }
        return res;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)


240. 搜索二维矩阵 II

编写一个高效的算法来搜索 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。

题解:
从右向左,从上到下逼近target
leetcode_数组与矩阵_第1张图片

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

时间复杂度O(m+n)
空间复杂度O(1)


378. 有序矩阵中第K小的元素

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

示例:

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

返回 13。

提示:
你可以假设 k 的值永远是有效的, 1 ≤ k ≤ n^2 。

题解:

  1. 转为一维数组
    原数组元素放在一维数组里,然后排序。
class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int n = matrix.length;
        int[] temp = new int[n*n];
        for (int i = 0; i<n*n; i++){
            temp[i] = matrix[i/n][i%n];
        }
        Arrays.sort(temp);
        return temp[k-1];
    }
}
  1. 大根堆
    维持堆中只有k个元素
    遍历完数组后,堆顶元素即为第k小的元素
class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int n = matrix.length;
        PriorityQueue<Integer> queue = new PriorityQueue<>(Collections.reverseOrder());
        
        for (int i = 0; i < n; i++){
            for (int j = 0; j < n; j++){
                queue.add(matrix[i][j]);
                if (queue.size()>k){
                    queue.remove();
                }
            }
        }
        return queue.peek();
    }
}
  1. 二分法
    最小的数为left = matirx[0][0], 最大的数为right = matrix[n-1][n-1]
    中间的数为 mid = (left+right)/2,那么可计算出小于等于mid的数为count
    如果count小于等于k,那么第k小的数在left = mid+1~right
    如果count大于k, 那么第k小的数在left ~ right = mid中。
    left == right 此时leftrigth就是第k小的数
class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int n = matrix.length;
        int left = matrix[0][0];
        int right = matrix[n-1][n-1];
        
        while(left < right){
            int mid = ((left+right)>>1);
            int count = help(matrix, mid, n);
            if (count < k){
                left = mid +1;
            }
            else{
                right = mid;
            }
        }
        return right;
    }
    
    public int help(int[][] matrix, int mid, int n){
        int i = 0;
        int j = n-1;
        int count = 0;
        while(i<n && j>=0){
            if (matrix[i][j] <= mid){
                count += j+1;
                i++;
            }
            else{
                j--;
            }
        }
        return count;
    }
}

645. 错误的集合

集合 S 包含从1到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复。
给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。

示例 1:

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

注意:
给定数组的长度范围是 [2, 10000]。
给定的数组是无序的。
题解:
位运算:异或
相同为0,不同为1
异或的性质:

  • 0^x = 0
  • x^x = 0
  • a^b^c = a^(b^c)

因此有a^b^b = a

  1. 首先通过异或计算出重复数a和丢失数b的异或值a^b
    将原数组的所有元素异或,该结果再和顺序数组[1,2,..,n]的每个元素异或,即可得到 a^b
  2. 分组:
    (1)从低位找到第一个a与b数值不同的位:a^b从低位开始第一个等于1的位,即为所求的位, 假设为p。
    (2)那么原数组和顺序数组中的所有元素分成两组: 第p位等于1的为1组,第p位等于0为一组。
    两个数组中一共有三个a和一个b,所以分组后一定可以分别异或出a和b.
    (3) 最后扫描一遍数组看a和b哪个是重复的数。
class Solution(object):
    def findErrorNums(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        res = 0
        for num in (nums):
            res ^= num
        for i in range(1, len(nums)+1):
            res ^= i
        
        # 确定缺失的数和重复的数首个不相同的那一位
        h = 1
        while res & h == 0:  # 首个不相同的那一位
            h <<= 1
        
        # 分组
        a = 0
        b = 0
        # nums分组
        for num in (nums):
            if num & h != 0:
                a ^= num
            else:
                b ^= num
        # 1...n 分组
        for i in range(1, len(nums)+1):
            if i & h != 0:
                a ^= i
            else:
                b ^= i
    
        # a b 看哪个是缺失的哪个是重复的
        for num in nums:
            if a == num:
                return [a, b]
        return [b, a]

时间复杂度O(n)
空间复杂度O(1)


667. 优美的排列 II

给定两个整数 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.

题解:
让前 k+1 个元素构建出 k 个不相同的差值。序列为[1, k+1, 2, k-1,…, ]
如当n = 8, k = 4时,让前5个元素构建出[1,5,2,4,3], 最后5个元素不变:[6,7,8]
k+1个元素两两之间差值的关系为:-k, k-1, -k-2,k-3,..., 1

class Solution {
    public int[] constructArray(int n, int k) {
        int[] res = new int[n];
        int j = k;
        res[0] = 1;
        for (int i = 1; i<=k; i++){
            if(i%2==1){
                res[i] = res[i-1] + j;
            }
            else{
                res[i] = res[i-1] - j;
            }
            j--;
        }
        for (int i = k+1; i<n; i++){
            res[i] = i+1;
        }
        return res;
    }
}

时间复杂度O(n)
空间复杂度O(n)


697. 数组的度

给定一个非空且只包含非负数的整数数组 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范围内的整数。

题解:
即要求包含所有出现次数等于度的元素的最小连续子数组,大小为right-left+1,right为最后一次出现的下标,left为第一次出现的下标。

  • 哈希表count记录每个元素出现的次数从而得到度。
  • 哈希表left记录每个元素第一次出现时的下标
  • 哈希表right记录每个元素最后一次出现时的下标
  • 出现次数与度相同的元素x计算最小数组大小:right.get(x) - left.get(x)+1
class Solution {
    public int findShortestSubArray(int[] nums) {
        Map<Integer, Integer> left = new HashMap<>();
        Map<Integer, Integer> right = new HashMap<>();
        Map<Integer, Integer> count = new HashMap<>();
        
        for (int i = 0; i<nums.length; i++){
            int num = nums[i];
            if (!left.containsKey(num)){
                left.put(num, i);
            }
            right.put(num, i);
            count.put(num, count.getOrDefault(num, 0) + 1);
        }
        
        int max = Collections.max(count.values()); // 出现次数最多的元素
        int res = nums.length;
        for (int num: count.keySet()){
            if (count.get(num) == max){
                res = Math.min(res, right.get(num)-left.get(num)+1);
            }
        }
        return res;
    }
}

时间复杂度O(n)
空间复杂度O(n)


766. 托普利茨矩阵

如果一个矩阵的每一方向由左上到右下的对角线上具有相同元素,那么这个矩阵是托普利茨矩阵。
给定一个 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]"上的元素不同。

说明:
matrix 是一个包含整数的二维数组。
matrix 的行数和列数均在 [1, 20]范围内。
matrix[i][j] 包含的整数在 [0, 99]范围内。
进阶:

如果矩阵存储在磁盘上,并且磁盘内存是有限的,因此一次最多只能将一行矩阵加载到内存中,该怎么办?
如果矩阵太大以至于只能一次将部分行加载到内存中,该怎么办?
题解
如果是托普利茨矩阵,那么每个元素和它右下角的元素相等,即matrix[i][j] == matrix[i+1][j+1]

class Solution {
    public boolean isToeplitzMatrix(int[][] matrix) {
        int n = matrix.length;
        if (n==0){
            return false;
        }
        int m = matrix[0].length;
        for (int i = 0; i<n-1; i++){
            for (int j=0; j<m-1; j++){
                if (matrix[i][j] != matrix[i+1][j+1]){
                    return false;
                }
            }
        }
        return true;
    }
}

时间复杂度O(m*n)
空间复杂度O(1)


565. 数组嵌套

索引从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}

注意:
N是[1, 20,000]之间的整数。
A中不含有重复的元素。
A中的元素大小在[0, N-1]之间。

题解:
由于数组中没有相同的元素,每个数字都唯一的对应一个完整环,每个数字都在某一个环中。
因此每一个环的形式都是:nums[x]=a ->...->nums[y] = x
而没有nums[x1]=b ->...-> nums[x2]=x, nums[x]=a ->...->nums[y] = x的形式,此时有两个元素为x

  • 以每个元素为开始计算每个环的大小,
  • 若某个起始元素已经被访问过,则该元素已构成环,不用再重复计算这个环的大小。
class Solution {
    public int arrayNesting(int[] nums) {
        int max = 0;
        for(int i = 0; i<nums.length; i++){
            int count = 0;
            int j = i;
            while (nums[j]!=-1){  // 1. 以当前元素为环的起始,若该元素使用过,则不进入循环。
                                  // 2. 访问过的元素置-1,那么碰到该环的起始元素时,也会跳出
                count++;
                int index = nums[j];
                nums[j] = -1;    // 访问过的元素置为-1
                j = index;
            }
        max = Math.max(max, count);
        }
        return max;
    }
}

时间复杂度O(n)
空间复杂度O(1)


769. 最多能完成排序的块

数组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]的一种排列。

题解:
首先找到从左块开始最小块的大小,如果k个数排序后能构成[0,1,2,3,...,k-1], 他们分为一个块。
接着前k+1个数排序后为[0,1,2,3,...,k-1, nums[k]], 若有 max(nums[0:k]) == k, 此时块数增`1,否则块数不变。

// 暴力
class Solution {
    public int maxChunksToSorted(int[] arr) {
        int max = 0;
        int res = 0;
        for (int i = 0; i<arr.length; i++){
            max = Math.max(max, arr[i]);
            if (max == i){
                res++;
            }
        }
        return res;
    }
}

时间复杂度O(n)
空间复杂度O(1)

你可能感兴趣的:(leetcode_数组与矩阵)