【LeetCode】数组

前缀和

labuladong的前缀和技巧

如何快速得到某个子数组的和呢,比如说给你一个数组 nums,让你实现一个接口 sum(i, j),这个接口要返回 nums[i…j] 的和,而且会被多次调用,你怎么实现这个接口呢?

因为接口要被多次调用,显然不能每次都去遍历 nums[i…j],有没有一种快速的方法在 O(1) 时间内算出 nums[i…j] 呢?这就需要前缀和技巧了。

前缀和的思路:对于一个给定的数组 nums,我们用一个前缀和数组preSum来存前缀和。
preSum[i+1],就是nums[0…i]的和

int n = nums.length;
// 前缀和数组
int[] preSum = new int[n + 1];
preSum[0] = 0;
for (int i = 0; i < n; i++)
    preSum[i + 1] = preSum[i] + nums[i];

那么如果我们想求 nums[i…j] 的和,只需要一步操作 preSum[j+1]-preSum[i] 即可,而不需要重新去遍历数组了。

一般还要结合hash表, 减少遍历次数

即用hash表来存

303. 区域和检索-数据不可变

给定一个整数数组 nums,处理以下类型的多个查询:

计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
实现 NumArray 类:

NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )


class NumArray {
    // 前缀和: preSum[i] 表示nums[0...i-1]的和
    private int[] preSum;

    public NumArray(int[] nums) {
        int n = nums.length;
        preSum = new int[n+1];

        for (int i = 1; i <= n; i++) {
            preSum[i] = preSum[i-1]+nums[i-1];
        }

    }
    
    public int sumRange(int left, int right) {
        return preSum[right+1]-preSum[left];
    }
}

304. 二维区域和检索-矩阵不可变

给定一个二维矩阵 matrix,以下类型的多个请求:

计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。
实现 NumMatrix 类:

NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和 。


自己做的是把每一行的前缀和加起来,但这样还是O(n)

class NumMatrix {

    // 记录每一行 的前缀和
    int[][] preSum;

    public NumMatrix(int[][] matrix) {
        int row = matrix.length;
        if (row==0){
            return ;
        }
        int col = matrix[0].length;

        preSum = new int[row][col+1];
        for (int r = 0;r<row;r++){
            for (int c = 1;c<=col;c++){
                preSum[r][c] = preSum[r][c-1]+matrix[r][c-1];
            }
        }
    }
    public int sumRegion(int row1, int col1, int row2, int col2) {

        int sum = 0;
        // 把row1到row2 的 和加起来即可
        for (int r = row1;r<=row2;r++){
            // 求r行的前缀和
            sum+=preSum[r][col2+1]-preSum[r][col1];
        }
        return sum;
    }
}

O(1)的做法:
在这里插入图片描述

class NumMatrix {
    // 定义:preSum[i][j] 记录 matrix 中子矩阵 [0, 0, i-1, j-1] 的元素和
    private int[][] preSum;
    
    public NumMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        if (m == 0 || n == 0) return;
        // 构造前缀和矩阵
        preSum = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                // 计算每个矩阵 [0, 0, i, j] 的元素和
                preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] + matrix[i - 1][j - 1] - preSum[i-1][j-1];
            }
        }
    }
    
    // 计算子矩阵 [x1, y1, x2, y2] 的元素和
    public int sumRegion(int x1, int y1, int x2, int y2) {
        // 目标矩阵之和由四个相邻矩阵运算获得
        return preSum[x2+1][y2+1] - preSum[x1][y2+1] - preSum[x2+1][y1] + preSum[x1][y1];
    }
}

523. 连续的子数组和

给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:

子数组大小 至少为 2 ,且
子数组元素总和为 k 的倍数。
如果存在,返回 true ;否则,返回 false 。

如果存在一个整数 n ,令整数 x 符合 x = n * k ,则称 x 是 k 的一个倍数。0 始终视为 k 的一个倍数。

示例 1:
输入:nums = [23,2,4,6,7], k = 6
输出:true
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6 。
示例 2:

输入:nums = [23,2,6,4,7], k = 6
输出:true
解释:[23, 2, 6, 4, 7] 是大小为 5 的子数组,并且和为 42 。
42 是 6 的倍数,因为 42 = 7 * 6 且 7 是一个整数。
示例 3:

输入:nums = [23,2,6,4,7], k = 13
输出:false

提示:

1 <= nums.length <= 105
0 <= nums[i] <= 109
0 <= sum(nums[i]) <= 231 - 1
1 <= k <= 231 - 1


用前缀和数组preSum 维护前缀和
遍历 这种暴力遍历方式在力扣差一个用例过不了

    public boolean checkSubarraySum(int[] nums, int k) {

        int n = nums.length;
        // 前缀和
        int[] preSum = new int[n+1];
        for (int i = 0; i < n; i++) {
            preSum[i+1] = preSum[i]+nums[i];
        }

        // nums[i..j]是否满足条件
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <i; j++) {
                int sum = preSum[i]-preSum[j];
                if(sum%k==0 && i-j>=1){
                    return true;
                }
            }
        }
        return false;
    }

优化, 第二层for循环就是在计算有j使得preSum[i]-preSum[j] 为k的倍数 ,如果preSum[i]-preSum[j] 为k的倍数,那么preSum[i]和preSum[j]除以k的余数相同。因此我们只需要计算每个下标对应的前缀和除以k的余数,用哈希表存每个余数第一次出现的下标

class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {

        int n = nums.length;
        if(n<2){
            return false;
        }

        HashMap<Integer/*余数*/,Integer/*第一次出现的下标*/> map = new HashMap<>();
        map.put(0,-1);
        int mod = 0;
        for (int i = 0; i < n; i++) {

            mod = (mod+nums[i])%k;
            if(map.containsKey(mod)){
            	// 这里两个if必须分开写,因为map存的是余数第一次出现的下标
                if(i-map.get(mod)>=2){
                    return true;
                }   
            }
            else{
                map.put(mod,i);
            }

        }
        return false;
    }
}

560. 和为K的子数组

560. 和为K的子数组
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :
数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。


看到连续子数组的和,肯定是用的前缀和的解法
也就是preSum[i]-preSum[j]==k 则ans++
但是如果两层遍历是On2,我们直接来用hash表优化
对于nums[i] 来说,其实就是要找preSum[j]==preSum[i]-k,那我们用哈希表存前缀和以及出现的次数,这样就是O1了

class Solution {
    public int subarraySum(int[] nums, int k) {

        int n = nums.length;
        // 用hash表存 前缀和以及此前缀和出现的次数
        Map<Integer,Integer> map = new HashMap<>();

        int sum = 0;
        int ans = 0;
        //nums[0]的前缀和是0
        map.put(0,1);
        for (int i = 0; i < n; i++) {

            sum+=nums[i];
            if(map.containsKey(sum-k)){
                ans+=map.get(sum - k);
            }

            map.put(sum,map.getOrDefault(sum,0)+1);
        }
        return ans;

    }
}

525. 连续数组

525. 连续数组

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。

提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1


思路:

由于求的是0和1个数相等的最长连续子数组,我们把数组中的0看成-1,题目就转化成了和为0的最长子数组

1744. 你能在你最喜欢的那天吃到你最喜欢的糖果吗?

给你一个下标从 0 开始的正整数数组 candiesCount ,其中 candiesCount[i] 表示你拥有的第 i 类糖果的数目。同时给你一个二维数组 queries ,其中 queries[i] = [favoriteTypei, favoriteDayi, dailyCapi] 。

你按照如下规则进行一场游戏:

你从第 0 天开始吃糖果。
你在吃完 所有 第 i - 1 类糖果之前,不能 吃任何一颗第 i 类糖果。
在吃完所有糖果之前,你必须每天 至少 吃 一颗 糖果。
请你构建一个布尔型数组 answer ,用以给出 queries 中每一项的对应答案。此数组满足:

answer.length == queries.length 。answer[i] 是 queries[i] 的答案。
answer[i] 为 true 的条件是:在每天吃 不超过 dailyCapi 颗糖果的前提下,你可以在第 favoriteDayi 天吃到第 favoriteTypei 类糖果;否则 answer[i] 为 false 。
注意,只要满足上面 3 条规则中的第二条规则,你就可以在同一天吃不同类型的糖果。

请你返回得到的数组 answer 。

示例 1:

输入:candiesCount = [7,4,5,3,8], queries = [[0,2,2],[4,2,4],[2,13,1000000000]]
输出:[true,false,true]
提示:
1- 在第 0 天吃 2 颗糖果(类型 0),第 1 天吃 2 颗糖果(类型 0),第 2 天你可以吃到类型 0 的糖果。
2- 每天你最多吃 4 颗糖果。即使第 0 天吃 4 颗糖果(类型 0),第 1 天吃 4 颗糖果(类型 0 和类型 1),你也没办法在第 2 天吃到类型 4 的糖果。换言之,你没法在每天吃 4 颗糖果的限制下在第 2 天吃到第 4 类糖果。
3- 如果你每天吃 1 颗糖果,你可以在第 13 天吃到类型 2 的糖果。
示例 2:

输入:candiesCount = [5,2,6,4,1], queries = [[3,1,2],[4,10,3],[3,10,100],[4,100,30],[1,3,1]]
输出:[false,true,true,false,false]

提示:

1 <= candiesCount.length <= 105
1 <= candiesCount[i] <= 105
1 <= queries.length <= 105
queries[i].length == 3
0 <= favoriteTypei < candiesCount.length
0 <= favoriteDayi <= 109
1 <= dailyCapi <= 109

旋转

189.轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]


先把整个翻转,再翻转[0,k%n-1] 和[k%n,n-1]

class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k = k%n;
        // 1、先将所有元素翻转
        reverse(nums, 0, n-1);
        // 2、翻转 [0,k-1]
        reverse(nums, 0, k-1);
        // 2、翻转[k,n-1]
        reverse(nums, k, n-1);
    }

    public void reverse(int[] nums, int start, int end){
        while(start<end){
            int tmp = nums[start];
            nums[start] = nums[end];
            nums[end] = tmp;
            start++;
            end--;
        }
    }
}

31、下一个排列

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。

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

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

示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]

示例 4:
输入:nums = [1]
输出:[1]

提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100


思路:
字典序的下一个排列,所以下一个排列比当前排列大一点点,所以我们应该找一个[较小数]和[较大数],将这两个数字交换,然后把后年的排列从小到大排序。
具体做法:

  1. 找较小数: 从后往前找第一个比后一个元素小的那个数
  2. 找较大数:从后往前找第一个比较小数大的数
  3. 交换较小数和较大数
  4. 交后半部分升序排列,由于后半部分原本一定是降序的,所以我们只需要用双指针把他逆序即可。
class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        // 1. 得到较小数
        int i = n-2;
        while(i>=0 && nums[i]>=nums[i+1]){
            i--;
        }

        if(i>=0){
            // 2. 得到较大数
            int j = n-1;
            while(j>=0 && nums[j]<=nums[i]){
                j--;
            }

            // 3. 交换较小数和较大数
            swap(nums,i,j);

        }

        // 4. 对[i+1,n) 进行升序排列 ,因为可以证明本身是升序,所以只需要双指针法反转即可
        int l = i+1,r = n-1;
        while(l<r){
            swap(nums,l,r);
            l++;
            r--;
        }
    }

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

    }
}

238. 除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法,且在 O(n) 时间复杂度内完成此题。

示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]


这个题要算出 前缀乘积和后缀乘积,除自己外的乘积就是前缀乘积*后缀乘积

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] preMultiply = new int[n+1];
        int[] postMulityply = new int[n+1];
        int[] ans = new int[n];
        preMultiply[0] = 1;
        postMulityply[n] = 1;
        // 前缀
        for(int i=1;i<=n;i++){
            preMultiply[i] = preMultiply[i-1]*nums[i-1];
        }
        // 后缀
        for(int i=n-1;i>=0;i--){
            postMulityply[i] = postMulityply[i+1]*nums[i];
        }
        for(int i=0;i<n;i++){
            ans[i] = preMultiply[i] * postMulityply[i+1];
        }
        return ans;
    }
}

区间

labuladong区间问题

56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104


  • 先以left将区间按从小到大排序
  • 然后从前往后判断每一个区间,看后面的区间是否与当前的区间重叠
class Solution {
    public int[][] merge(int[][] intervals) {
        if (intervals==null || intervals.length==0){
            return new int[][]{};
        }

        // 按start从小到大排序
        Arrays.sort(intervals, Comparator.comparingInt(a->a[0]));
        List<int[]> res = new ArrayList<>();
        res.add(intervals[0]);
        for (int i=1;i<intervals.length;i++){
            int[] cur = intervals[i];
            int[] last = res.get(res.size()-1);
            // 如果区间重叠 则 找最大的end
            if (cur[0]<=last[1]){
                last[1] = Math.max(cur[1],last[1]);
            }else{
                // 区间不重叠,则是新的区间
                res.add(cur);
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

986. 区间列表的交集

给定两个由一些 闭区间 组成的列表,firstList 和 secondList ,其中 firstList[i] = [starti, endi] 而 secondList[j] = [startj, endj] 。每个区间列表都是成对 不相交 的,并且 已经排序 。
返回这 两个区间列表的交集 。
形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,而 a <= x <= b 。
两个闭区间的 交集 是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3] 。
输入:firstList = [[0,2],[5,10],[13,23],[24,25]], secondList = [[1,5],[8,12],[15,24],[25,26]]
输出:[[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
示例 2:
输入:firstList = [[1,3],[5,9]], secondList = []
输出:[]
示例 3:
输入:firstList = [], secondList = [[4,8],[10,12]]
输出:[]
示例 4:
输入:firstList = [[1,7]], secondList = [[3,10]]
输出:[[3,7]]


需要搞清楚的三个问题:

  1. 如何判断交集
    // 有交集
    if(a2>=b1 && a1<=b2)
    
  2. 是交集如何合并
    res.add(new int[]{Math.max(a1,b1), Math.min(a2,b2)});
    
  3. 指针如何移动
    是否前进,只取决于 a2 和 b2 的大小关系
            // 移动指针
          if (a2<b2){
              i++;
          }else{
              j++;
          }

题解:

class Solution {
    public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {

        int i=0,j=0;
        List<int[]> res = new ArrayList<>();
        while (i<firstList.length && j<secondList.length){
            int a1 =firstList[i][0], a2 = firstList[i][1];
            int b1 =secondList[j][0], b2 = secondList[j][1];
            // 有交集
            if(a2>=b1 && a1<=b2){
                res.add(new int[]{Math.max(a1,b1), Math.min(a2,b2)});
            }
            // 移动指针
            if (a2<b2){
                i++;
            }else{
                j++;
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

1288.删除被覆盖区间

给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。
只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。
在完成所有删除操作后,请你返回列表中剩余区间的数目。
示例:
输入:intervals = [[1,4],[3,6],[2,8]]
输出:2
解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了。


剩余区间数 = 总数-被删除区间数

  1. 先按start从小到大排序,start相等则end倒序
  2. 遍历数组,一共有三种情况
    • 覆盖 res++
    • 区间相交,更新区间的终点
    • 完全不相交,更新新起点和终点
class Solution {
    public int removeCoveredIntervals(int[][] intervals) {
        // 被删除的区间
        int res = 0;
        // 按start从小到大排序,start一样则end从大到小排序
        Arrays.sort(intervals, (a,b)->{
            if (a[0]==b[0]){
                // start一样,按end倒序排
                return b[1]-a[1];
            }
            return a[0]-b[0];
        });
        // 记录合并区间的起点和终点
        int left = intervals[0][0];
        int right = intervals[0][1];
        for (int i = 1; i < intervals.length; i++) {

            int[] inv = intervals[i];
            // 情况一:区间被覆盖
            if (inv[0]>=left && inv[1]<=right){
                res++;
            }
            // 情况二:区间相交
            if (inv[0]<=right && right<=inv[1]){
                right = inv[1];
            }
            // 情况三:完全不相交,更新新起点和终点
            if (right<inv[1]){
                left = inv[0];
                right = inv[1];
            }

        }

        return intervals.length-res;
    }
}

41. 缺失的第一个正数

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
示例 1:

输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。
示例 2:

输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。
示例 3:

输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。


  1. 找到规律:对于按理想情况排列的数组,每个元素nums[i]==i+1
  2. 如何让一个杂乱的数组变成理想的数组: 就是遍历nums[i] 对于在[1,n]区间内,且不是在自己位置上的 就交换
        for(int i=0;i<n;i++){
           // 满足在指定范围内、并且没有放在正确的位置上,才交换
            while(nums[i]>0 && nums[i]<=n && nums[nums[i]-1]!=nums[i]){
                 // nums[i]的正确位置是nums[i]-1
                swap(nums, nums[i]-1, i);
            }
        }

在这里插入图片描述
在这里插入图片描述

class Solution {
    public int firstMissingPositive(int[] nums) {

        int n = nums.length;
        // 先把元素放到指定位置
        for(int i=0;i<n;i++){
           // 满足在指定范围内、并且没有放在正确的位置上,才交换
            while(nums[i]>0 && nums[i]<=n && nums[nums[i]-1]!=nums[i]){
                 // nums[i]的正确位置是nums[i]-1
                swap(nums, nums[i]-1, i);
            }
        }
        // 找到第一个不满足要求的元素下标
        for(int i=0;i<n;i++){
            if(nums[i]!=i+1){
                return i+1;
            }
        }
        return n+1;
    }

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

矩阵

73. 矩阵置0

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。


  1. 先记录第一行、第一列是否有0
  2. 把第一行第一列作为标志位,遍历矩阵matrix[i][j]为0则置对应第一列和对应第一行行为0
  3. 根据第一行第一列是否为0,将matrix[i][j]置0
  4. 根据第一行、第一列是否有0的记录,将第一行第一列置0
class Solution {
    public void setZeroes(int[][] matrix) {

        int row = matrix.length;
        int col = matrix[0].length;
        // 先记录第一行第一列是否有呢
        boolean first_row = false;
        boolean first_col = false;
        // 记录第一行是否有0
        for(int i=0;i<col;i++){
            if(matrix[0][i]==0){
                first_row = true;
                break;
            }
        }
        // 记录第一列是否有0
        for(int i=0;i<row;i++){
            if(matrix[i][0]==0){
                first_col = true;
                break;
            }
        }
        // 把第一行第一列作为标志位,记录对应行列是否有0
        for(int i=1;i<row;i++){
            for(int j=1;j<col;j++){
                if(matrix[i][j]==0){
                    matrix[i][0] = 0;
                    matrix[0][j] = 0;
                }
            }
        }
        // 置0
            for(int i=1;i<row;i++){
                for(int j=1;j<col;j++){
                if(matrix[i][0]==0 || matrix[0][j]==0){
                    matrix[i][j] = 0;
                }
            }
        } 
        // 对第0行和第0列置0
        if(first_row){
             for(int i=0;i<col;i++){
                    matrix[0][i] = 0;
             }    
        }
        if(first_col){
            for(int i=0;i<row;i++){
                matrix[i][0] = 0;
             }
        }
    }
}

54. 螺旋矩阵

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。


根据题目示例 matrix = [[1,2,3],[4,5,6],[7,8,9]] 的对应输出 [1,2,3,6,9,8,7,4,5] 可以发现,顺时针打印矩阵的顺序是 “从左向右、从上向下、从右向左、从下向上” 循环。

因此,考虑设定矩阵的 “左、上、右、下” 四个边界,模拟以上矩阵遍历顺序。
可以想象是四条线,每遍历一边对应线就向内收缩,收缩到越界则break
在这里插入图片描述

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        int l = 0,r=matrix[0].length-1,t=0,b=matrix.length-1;
        int x=0;
        Integer[] res = new Integer[(r+1)*(b+1)];
        while(true){
            // 从左到右遍历
            for(int i=l;i<=r;i++){
                res[x++] = matrix[t][i];
            }
            if(++t>b){
                break;
            }
            // 从上到下遍历
            for(int i=t;i<=b;i++){
                res[x++] = matrix[i][r];
            }
            if(--r<l){
                break;
            }
            // 从右到左遍历
            for(int i=r;i>=l;i--){
                res[x++] = matrix[b][i];
            }
            if(--b<t){
                break;
            }
            // 从下到上遍历
            for(int i=b;i>=t;i--){
                res[x++] = matrix[i][l];
            }   
            if(++l>r){
                break;
            }
        }
        return Arrays.asList(res);
    }
}

48. 旋转图像

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。


矩阵的每个元素matrix[i][j]换完后的位置是matrix[j][n-1-i]

  1. 我们以位于矩阵四个角点的元素为例,设矩阵左上角元素 A 、右上角元素 B 、右下角元素 C 、左下角元素 D 。矩阵旋转 90º 后,相当于依次先后执行 D→A , C→D , B→C , A→B 修改元素,可以把A的元素放在tmp存储
    在这里插入图片描述
  2. 这样一轮是四个元素,因而,只要分别以矩阵左上角 1/4 的各元素为起始点执行以上旋转操作,即可完整实现矩阵旋转。
    当矩阵大小 n 为偶数时,取前 n/2行,前n/2列;当矩阵大小 n 为奇数时,取前 (n+1)/2行,前n/2列的元素为起始点。
  3. 具体换就是根据矩阵的每个元素matrix[i][j]换完后的位置是matrix[j][n-1-i]
    在这里插入图片描述
class Solution {
    public void rotate(int[][] matrix) {

        int n = matrix.length;
        for (int i=0;i<n/2;i++){
            for (int j = 0; j < (n + 1) / 2; j++) {
                int tmp = matrix[i][j];
                // 元素旋转操作 A <- D <- C <- B <- tmp
                matrix[i][j] = matrix[n-1-j][i];
                matrix[n-1-j][i] = matrix[n-1-i][n-1-j];
                matrix[n-1-i][n-1-j] = matrix[j][n-1-i];
                matrix[j][n-1-i] = tmp;
            }
        }
    }
}

240.搜索二维矩阵②

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。


每行二分

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length;
        int col = matrix[0].length;

        for (int i = 0; i < row; i++) {
            // 二分查找
            int left = 0,right = col-1;
            // 搜索区间[l,r]
            while(left<=right){
                int mid = left + (right-left)/2;
                if (matrix[i][mid]==target){
                    return true;
                }else if(matrix[i][mid]>target){
                    right = mid-1;
                }else if(matrix[i][mid]<target){
                    left = mid+1;
                }
            }
        }
        return false;
    }
}

74.搜索二维矩阵

给你一个满足下述两条属性的 m x n 整数矩阵:

每行中的整数从左到右按非严格递增顺序排列。
每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。


这个每行的第一个数大于上一行最后一个数,所以可以把整个矩阵看成一个一维数组
int x = mid/col;
int y = mid%col;

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {

        int row = matrix.length;
        int col = matrix[0].length;
        int n = row * col;

        int left=0,right = n-1;
        while(left<=right){
            int mid = left+(right-left)/2;
            int x = mid/col;
            int y = mid%col;
            if (matrix[x][y]==target){
                return true;
            }else if(matrix[x][y]>target){
                right = mid-1;
            }else if(matrix[x][y]<target){
                left = mid+1;
            }
        }
        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值