Leetcode复盘6——数组与矩阵

Leetcode复盘6——数组与矩阵

导读
1.移动零 / 把数组中的 0 移到末尾相同(Leetcode283)

难度:简单Easy

idea:
定义一个游动下标idx,先把nums里非零数字放到前面,再把后面的位置置0
取数组大小,Java里是nums.length,C++是nums.size()

代码:
C++版

class Solution {
public:
    void moveZeroes(vector<int>& nums) {                  // 可变数组vector,里面放的是int型元素,&取地址
        int idx = 0;
        // 先放非零单位
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] != 0) {
                nums[idx++] = nums[i];
            }
        }
        // 再放0单位
        while(idx < nums.size()) {
            nums[idx++] = 0;
        }
    }
};

idea:
定义一个游动下标idx,先把nums里非零数字放到前面,再放0

Java版

class Solution {
    public void moveZeroes(int[] nums) {
        int idx = 0;
        // 先放非零单位
        for (int num : nums) {
            if (num != 0) {                                 // 对于某个非零数字num
                nums[idx++] = num;                          // 把它放到对应位置,idx往右移一位
            }
        }
        // 再放0单位
        while (idx < nums.length) {                         // 当idx还没走到[nums.length-1]时
            nums[idx++] = 0;
        }
    }
}
2.重塑矩阵 / 改变矩阵维度(Leetcode566)

难度:简单Easy

idea:矩阵

代码
C++版

class Solution {
public:
    vector<vector<int>> matrixReshape(vector<vector<int>>& nums, int r, int c) {
        int x = nums.size(), y = nums[0].size();                    // 因为写在一行,故中间用逗号
        if (x * y != r * c) return nums;
        // 解释: 定义二维动态数组res: vector>,包含:
        // r个初始值为vector(c, 0)的一维数组
        // vector(c, 0): c个值为0的元素
        vector<vector<int>> res(r, vector<int>(c, 0));
        for (int i = 0; i < r * c; ++i)
            res[i / c][i % c] = nums[i / y][i % y];
        return res;
    }
};

idea: Python切片
先通过双重for循环把nums所有的元素放到1维res里,第一层for j in nums,即j=[1,2]或j=[3,4]; 第二层for i in
j,i = 1或2;3或4, res = [1,2,3,4]
range(0, len(res), c),从0到len(res)每次前进c步,每次取c个元素([i:i+c])

Python版

class Solution:
    def matrixReshape(self, nums: List[List[int]], r: int, c: int) -> List[List[int]]:
        m, n = len(nums), len(nums[0])
        if m * n != r * c:
            return nums
        res = [i for j in nums for i in j]
        return [res[i: i + c] for i in range(0, len(res), c)]
3.最大连续 1 的个数 / 找出数组中最长的连续 1(LeetCode485)

难度:简单Easy

idea: 常规思路
分两步:
1.若当前数字还是1,则count++;
2.若变成0了,比较count和max的大小,并且count重新归0

代码:
Java版

class Solution {
    public int findMaxConsecutiveOnes(int[] nums) {
        int length = nums.length;
        int max  = 0;                                       // 记录最长时1的个数
        int count = 0;                                      // 记录当前1的个数
        
        // 遍历数组
        for (int i = 0; i < length; i++){
            if (nums[i] == 1){                              // 若当前的数字为1,count就加1
                count++;
            } else {                                        // 若当前数字不为1了
                if (count > max)
                    max = count;
                // 归零,寻找下一个连续1序列
                count = 0;
            }
        }
        
        // 因为最后一次连续序列在循环中无法比较,所以在循环外进行比较
        return max > count? max : count;
    }
}

idea: 滑动串口法(sliding window)
将定义左右指针left和right,把两个指针之间的距离比作一个窗口,通过移动指针的位置改变窗口的大小,观察窗口中的元素是否符合题意
分两步:
1.起始时左右指针指向一个数字,窗口为0;当窗口中所有元素为1时,右指针向右移,扩大窗口;
2.当窗口中存在0时,计算连续1序列的长度,左指针直接跳到右指针位置上,窗口从0开始
e.g
111001
left=[0],right=[2],判断if条件不成立,right+1=3
left=[0],right=[3],判断if条件成立,right+1=4,maxSize=[4]-[0]-1=3,left -> 4

Java版

class Solution {
    public int findMaxConsecutiveOnes(int[] nums) {
        int length = nums.length;
        int left = 0;
        int right = 0;                                      // right和left之间的大小即为当前窗口的大小
        int maxSize = 0;                                    // 记录最大窗口的大小
        
        while(right < length) {                             // 当右指针还没到头时
            // 当窗口中所有元素为1时,右指针向右移,扩大窗口
            if (nums[right++] == 0) {                       // 这里很巧妙,把right+1放到if的语句里
                // 当窗口中存在0时,计算连续序列长度,左指针指向右指针
                maxSize = Math.max(maxSize, right - left - 1);
                left = right;
            }
        }
        // 因为最后一次连续序列在循环中无法比较,所以在循环外进行比较
        return Math.max(maxSize, right - left);
    }
}
4.搜索二维矩阵 II / 有序矩阵查找(LeetCode240)

难度:中等Medium

idea: 二分查找(binary search)
每到新的一行,看两件事:
1.第一个数[i][0]是否大于了target,大于了就结束了,因为后面的都比它大,break跳出循环结束;
2.最后一个数[i][length-1]是否小于了target,若小于了直接去下一行,因为最后一个数是当前行最大的,它小于了肯定别的数都小于target
3.找到合适的一行,用二分查找把target找出来

代码:
Java版

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if (matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        for (int i = 0; i < matrix.length; i++) {
            if (matrix[i][0] > target) {
                break;
            }
            if(matrix[i][matrix[i].length - 1] < target){
                continue;
            } 
            int col = binarySearch(matrix[i], target);
            if (col != -1) {
                return true;
            }
        }
        return false;
    }

    //二分查找
    private int binarySearch(int[] nums, int target) {
        int start = 0;
        int end = nums.length - 1;
        while (start <= end) {
            int mid = (start + end) >>> 1;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                start = mid + 1;
            } else {
                end = mid - 1;
            }
        }
        return -1;
    }
}

idea: 奇技淫巧
因为数组从左到右和从上到下都是升序的,故从右上角出发开始遍历,右上角数字有个特点:
1.在其所在列上它最小,因为往下都比它大;
2.在其所在行上它最大,因为往左都比它小;

故我们可以把target和当前值比较:
1.如果target的值大于当前值,那么就向下走 (即当前值所在行肯定不行了,因为那一行当前值最大)
2.如果target的值小于当前值,那么就向左走 (即当前值所在列肯定不行了,因为那一列当前值最小)
3.如果相等直接返回true

Java版

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if (matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        int row = 0;
        int col = matrix[0].length - 1;
        while (row < matrix.length && col >= 0) {               // 向下没走到底且向左没走到开头
            if (target > matrix[row][col]) {
                row++;
            } else if (target < matrix[row][col]) {
                col--;
            } else {
                return true;
            }
        }
        return false;
    }
}
6.错误的集合 / 一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数(LeetCode645)

idea:
通过交换数组元素,使得数组上的元素在正确的位置上,分两步:
1.当nums[i] != i+1时,证明出问题了,给它找到合适的位置,nums[nums[i]-1]是nums[i]该待的位置,
2.交换nums[i]放到nums[nums[i]-1]位置上

代码:
Java版

class Solution {
    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;
    }
    // 交换nums[i]到nums[nums[i]-1]位置上
    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}
7.寻找重复数 / 找出数组中重复的数,数组值在 [1, n] 之间(LeetCode287)

idea: 二分法+抽屉原理
抽屉原理:桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,会发现至少会有一个抽屉里面放不少于两个苹果
二分法:先猜一个数,中间数 mid,然后统计原始数组中小于等于这个中间数的元素的个数cnt,如果cnt,严格大于mid,根据抽屉原理,说明重复元素就在区间[left, mid]里
e.g
nums = [2,4,5,2,3,1,6,7], mid = 4
统计nums中小于等于4的个数,正常的应该是4个,即[1,2,3,4],但此处统计出来是cnt=5,说明重复的数字在[1,4]之间,
下一轮mid = (1+4)/2=2,即统计[1,2]之间的数字个数

代码:
C++版

class Solution {
public:
    int findDuplicate(vector<int> &nums) {
        int len = nums.size();
        int left = 1;
        int right = len - 1;

        while (left < right) {
            // 在Java里可以这么用:(left + right)>>>1,当left + right溢出的时候,无符号右移保证结果依然正确
            int mid = left + (right - left) / 2;                // C++和Python里只能用这种保险的方法

            int cnt = 0;
            for (int num:nums) {
                if (num <= mid) {
                    cnt++;
                }
            }

            // 根据抽屉原理,小于等于4的个数如果严格大于4个,则此时重复元素一定出现在[1,4]区间里
            if (cnt > mid) {
                // 重复元素位于区间[left, mid]
                right = mid;
            } else {
                // [left,mid]中的数字如果没问题,则重复的数字一定在后半区间,即[mid + 1, right]
                left = mid + 1;
            }
        }
        return left;                                            // 二分查找返回left即可
    }
};

Java版

class Solution {
    public int findDuplicate(int[] nums) {
        int len = nums.length;
        int left = 1;
        int right = len - 1;
        while (left < right) {
            // 在Java里可以这么用,当left + right溢出的时候,无符号右移保证结果依然正确
            int mid = (left + right) >>> 1;
            
            int cnt = 0;
            for (int num : nums) {
                if (num <= mid) {
                    cnt += 1;
                }
            }

            // 根据抽屉原理,小于等于4的个数如果严格大于4个,则此时重复元素一定出现在[1,4]区间里
            if (cnt > mid) {
                // 重复元素位于区间[left, mid]
                right = mid;
            } else {
                // [left,mid]中的数字如果没问题,则重复的数字一定在后半区间,即[mid + 1, right]
                left = mid + 1;
            }
        }
        return left;                                   // 二分查找返回left即可
    }
}
9.数组的度 (LeetCode697)

idea:
分两步:
1.找原数组的度,即出现频次最大的数: 哈希表,key是数字,value是其出现的频次
2.再求与原数组相同度的最短子数组: 要求的最短子数组的起始和终止位置, 由出现次数最多的元素的第一次和最后一次出现的位置确定,但有一种特殊情况,即出现次数最多的元素可能不止一个,故把它们的子数组都得到再比较
分两步:
1.使用left和right分别保存了每个元素在数组中第一次出现的位置和最后一次出现的位置,使用counter保存每个元素出现的次数,数组的度degree等于counter.values()的最大值
2.对counter再次遍历,如果元素 k 出现的次数等于degree,则找出元素k最后一次出现的位置和第一次出现的位置,计算两者之差+1,即为子数组长度; 对所有出现次数等于degree的子数组的最短长度,取min

代码
C++版

class Solution {
public:
    int findShortestSubArray(vector<int>& nums) {
        // 定义3个哈希表,其中left和right的key为数字,value为出现的下标; counter的key为数字,value为出现的次数
        unordered_map<int, int> left, right, counter;
        int degree = 0;
        for(int i = 0; i < nums.size(); ++i) {
            if(!left.count(nums[i]))                         // 若left已经有nums[i]了,则不执行此句
                left[nums[i]] = i;
            right[nums[i]] = i;
            counter[nums[i]]++;
            degree = max(degree, counter[nums[i]]);
        }
        int res = nums.size();
        for (auto& kv : counter) {
            if (kv.second == degree) {                       // first是key,second是value
                res = min(res, right[kv.first] - left[kv.first] + 1);
            }
        }
        return res;
    }
};
10.托普利茨矩阵 / 对角元素相等的矩阵(LeetCode766)

idea:
每到新的一行,判断当前元素[i][j]和右下角的元素[i+i][j+1]是否相等即可;
对于Python,可以采用切片的方法整体判断,即判断第[i]行的[0,N-2]和第[i+1]行的[1,N-1]是否相等即可;

代码:
C++版

class Solution {
public:
    bool isToeplitzMatrix(vector<vector<int>>& matrix) {
        for (int i = 0; i < matrix.size() - 1; ++i) {
            for (int j = 0; j < matrix[0].size() - 1; ++j) {
                if (matrix[i][j] != matrix[i + 1][j + 1])
                    return false;
            }
        }
        return true;
    }
};

Java版

class Solution {
    public boolean isToeplitzMatrix(int[][] matrix) {
        for (int i = 0; i < matrix.length - 1; ++i) {               // 一直比到倒数第二行,最后一行用[i+1]即可
            for (int j = 0; j < matrix[0].length - 1; ++j) {        // 每行都比到倒数第二列,最后一列用[j+1]即可
                if (matrix[i][j] != matrix[i + 1][j + 1])
                    return false;
            }
        }
        return true;
    }
}

Python版

class Solution:
    def isToeplitzMatrix(self, matrix: List[List[int]]) -> bool:
        for i in range(len(matrix) - 1):
            # matrix[i][:-1]: 第[i]行从第[0]个取到倒数第二个([-1]取不到)
            # matrix[i + 1][1:]: 第[i+1]行从第[1]个取到最后一个
            if matrix[i][:-1] != matrix[i + 1][1:]:
                return False
        return True
12.最多能完成排序的块 / 分隔数组(LeetCode769)

idea:
目的是找当前块的最右端,当最右端与下标相等时,即可成一个新的块.
不断更新当前区间的最右端:right = max(right, arr[i]), 然后看下标i是否与最右端的值right相等,若相等,则表示可以切割成一个新的块,举例如下:
e.g: arr = [1,0,2,3,4], right = 0, res = 0
第一轮: i = 0, right = max(right, arr[i]) = max(0, arr[0]) = max(0, 1) = 1, i != right
(此时最右端为1,当前下标为0,不能切割,因为我们要把0放到最开头)
第二轮: i = 1, right = max(right, arr[i]) = max(1, arr[1]) = max(1, 0) = 1, i == right
(此时最右端为1,当前下标也为1,可以切割了,因为我们可以通过排序把0和1的顺序颠倒过来.

代码:
C++版

class Solution {
public:
    int maxChunksToSorted(vector<int>& arr) {
        if (arr.empty()) return 0;
        int right = 0;
        int res = 0;
        for (int i = 0; i < arr.size(); ++i) {
            right = max(right, arr[i]);
            if (i == right) {
                ++res;
            }
        }
        return res;
    }
};

你可能感兴趣的:(Leetcode复盘6——数组与矩阵)