代码随想录|DAY2| 209.长度最小的子数组,59.螺旋矩阵II,区间和,开发商购买土地+数组简单总结

209.长度最小的子数组

题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/

文章讲解:https://programmercarl.com/0209.%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.html

视频讲解:https://www.bilibili.com/video/BV1tZ4y1q7XE

状态:没做出来(不会滑动窗口)

思路

使用滑动窗口,不断调节子序列的起始位置终止位置。(可以理解为暴力求法的升级,将双重for需要干的事情在一层for里搞定)

在本题中就是先不断移动终止位置找到满足总和大于等于 target 的长度的子数组,然后再调节起始位置找到长度最小的子数组。

注意事项

1.用一个for循环,索引应该表示滑动窗口的起始位置,还是终止位置?

索引一定是滑动窗口的终止位置,不然会变回暴力求解。

2.移动起始位置的时候应该是while而不是if,因为if未必能找到最短的子数组。【易错,我犯错了】

3.时间复杂度是O(n)

时间复杂度要换个视角看: 看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

代码一【滑动窗口】(需要二刷)

思路:先不断移动终止位置找到满足总和大于等于 target 的长度的子数组,然后再调节起始位置找到长度最小的子数组。

class Solution {
    // 滑动窗口
    public int minSubArrayLen(int s, int[] nums) {
        // 起始位置
        int left = 0;
        // 记录当前窗口值
        int sum = 0;
        // 记录子数组的最小长度,设置成最大值方便更新
        int result = Integer.MAX_VALUE;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            // 当前结果满足条件,开始找最短长度
            // 记得用while更新最短结果(易错)
            while (sum >= s) {
                // 更新最短长度和当前窗口值
                result = Math.min(result, right - left + 1);
                sum -= nums[left++];
            }
        }
        // 如果是MAX_VALUE说明没有窗口符合条件,长度为0
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码二【暴力求解】(我的解法/超时)

思路:双层for遍历(外循环控制起始位置,内循环控制终止位置)

代码略

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

59.螺旋矩阵II

题目链接:https://leetcode.cn/problems/spiral-matrix-ii/

文章讲解:https://programmercarl.com/0059.%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5II.html

视频讲解:https://www.bilibili.com/video/BV1SL4y1N7mV/

状态:不看提示没思路,思路知道了就写出来了

思路

把题目过程模拟出来即可,按边模拟

注意事项

1.究竟是左闭右开还是左闭右闭

2.四条边退出循环的条件,注意起始位置、终止位置、以及数组下标的移动

到底是++,还是–

代码【模拟过程】(我的解法)

顺着过程模拟的思路比代码随想录的算圈数好懂一点(仅我个人而言,所以这里放的是我个人的代码,没有参考规范)

class Solution {
    public int[][] generateMatrix(int n) {
        // 创建数组
        int [][] nums=new int[n][n];
        // 数组边界位置
        int colstloc=0;
        int rowstloc=0;
        int rowedloc=n-1;
        int coledloc=n-1;
        // 注意不需要num++,内存循环自己会更新
        for(int num=1;num<=n*n;){
            // 模拟四条边的过程
            // 1-2-3
            for(int col=colstloc;col<=coledloc;col++){
                nums[rowstloc][col]=num++;
            }
            // 修改边界条件
            rowstloc++;
            if (num>n*n) {
                break;
            }
            // 3-4-5
            for(int row=rowstloc;row<=rowedloc;row++){
                nums[row][coledloc]=num++;
            }
            // 修改边界条件
            coledloc--;
            if (num>n*n) {
                break;
            }
            // 5-6-7
            for(int col=coledloc;col>=colstloc;col--){
                nums[rowedloc][col]=num++;
            }
            // 修改边界条件
            rowedloc--;
            if (num>n*n) {
                break;
            }
            // 7-8
            for(int row=rowedloc;row>=rowstloc;row--){
                nums[row][colstloc]=num++;
            }
            // 修改边界条件
            colstloc++;
            if (num>n*n) {
                break;
            }
        }
        return nums;
    }
}
  • 时间复杂度 O(n^2)【 模拟遍历二维矩阵的时间
  • 空间复杂度 O(1)

卡码网-58.区间和

题目链接:https://kamacoder.com/problempage.php?pid=1070

文章讲解:https://www.programmercarl.com/kamacoder/0058.%E5%8C%BA%E9%97%B4%E5%92%8C.html

状态:只会暴力求解,前缀和没概念

思路

借助长度为n的一维数组A[n]记录从起始位置到当前下标位置的总和(前缀和),需要i到j位置时,直接a[j]-a[i-1]即可

注意事项

1.注意减时,不要多减(a[j]-a[i]),i-1要注意i=0的条件(不然会越界)。

2.Scanner的用法回顾

// 引入包
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
    	// 创建
    	Scanner scanner = new Scanner(System.in);
        // 相关方法
    	int n = scanner.nextInt();
        while (scanner.hasNextInt()) {
          ...
        }
        // 关闭
        scanner.close();
    }
}

代码【前缀和】(没做出来)

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int n = scanner.nextInt();
        int[] vec = new int[n];
        int[] p = new int[n];
		//  计算0-i位置的前缀和
        int presum = 0;
        for (int i = 0; i < n; i++) {
            vec[i] = scanner.nextInt();
            presum += vec[i];
            p[i] = presum;
        }
		// 计算指定下标的和
        while (scanner.hasNextInt()) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();

            int sum;
            if (a == 0) {
                sum = p[b];
            } else {
                sum = p[b] - p[a - 1];
            }
            System.out.println(sum);
        }

        scanner.close();
    }
}
  • 时间复杂度 O(max(n,v)) 【n为给定数组大小,v为要求趟数】
  • 空间复杂度 O(n)【借助长度为n的数组存放前缀和】

卡码网-44. 开发商购买土地

题目链接:https://kamacoder.com/problempage.php?pid=1044

文章讲解:https://www.programmercarl.com/kamacoder/0044.开发商购买土地.html#思路

状态:只会暴力解法,前缀和思路卡住了

思路

把每一行(列)的总和计算出来,按行(列)切分,就是行(列)的前缀和求区间最大值

注意事项

  1. 同上一题的前缀和注意点
  2. 因为是二分,所以必定是0到i,i到n-1行(列),所以求前缀和的过程中代码可以优化

代码【前缀和】(没做出来)

未优化:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int sum = 0;
        int[][] vec = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                vec[i][j] = scanner.nextInt();
                sum += vec[i][j];
            }
        }

        // 统计横向
        int[] horizontal = new int[n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                horizontal[i] += vec[i][j];
            }
        }

        // 统计纵向
        int[] vertical = new int[m];
        for (int j = 0; j < m; j++) {
            for (int i = 0; i < n; i++) {
                vertical[j] += vec[i][j];
            }
        }

        int result = Integer.MAX_VALUE;
        int horizontalCut = 0;
        for (int i = 0; i < n; i++) {
            horizontalCut += horizontal[i];
            result = Math.min(result, Math.abs(sum - 2 * horizontalCut));
        }

        int verticalCut = 0;
        for (int j = 0; j < m; j++) {
            verticalCut += vertical[j];
            result = Math.min(result, Math.abs(sum - 2 * verticalCut));
        }

        System.out.println(result);
        scanner.close();
    }
}
  • 时间复杂度 O(n^2)
  • 空间复杂度 O(n)

优化:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int sum = 0;
        int[][] vec = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                vec[i][j] = scanner.nextInt();
                sum += vec[i][j];
            }
        }

        int result = Integer.MAX_VALUE;
        int count = 0; // 统计遍历过的行和

        // 行切分(已优化)
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                count += vec[i][j];
                // 遍历到行末尾时候开始统计
                if (j == m - 1) {
                    // Math.abs(sum - 2 * count)为A公司(sum-count)-B公司(count)的绝对值
                    result = Math.min(result, Math.abs(sum - 2 * count));
                }
            }
        }

        count = 0;
        // 列切分
        for (int j = 0; j < m; j++) {
            for (int i = 0; i < n; i++) {
                count += vec[i][j];
                // 遍历到列末尾时候开始统计
                if (i == n - 1) {
                    result = Math.min(result, Math.abs(sum - 2 * count));
                }
            }
        }

        System.out.println(result);
        scanner.close();
    }
}

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

总结

注意事项

1.左闭右开还是左闭右闭(我习惯左闭右闭)——>不关注容易下标越界

左闭右闭:一般要n-1,取=号

涉及题型

1.二分法

2.双指针法:左右指针/快慢指针【快慢指针不太会】

3.滑动窗口【不会】:暴力求法(双层for)优化成一轮for,所以终止条件为for的循环条件

4.模拟行为:顺着题目耐心写就行

5.前缀和【不会】:空间换时间的思想,记录至当前位置的前缀和,注意减的时候不要减多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值