代码随想录刷题day2丨209.长度最小的子数组,59.螺旋矩阵II,区间和,开发商购买土地,数组专题总结
1. 题目
1.1长度最小的子数组
-
题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/
-
视频讲解:https://www.bilibili.com/video/BV1tZ4y1q7XE
-
文档讲解: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
-
解题思路:暴力解法或者双指针解法
-
代码
-
暴力解法:两个for循环,一个控制起始位置,另一个控制终止位置
//时间复杂度:O(n^2) //空间复杂度:O(1) class Solution { public int minSubArrayLen(int target, int[] nums) { int result = Integer.MAX_VALUE; int sum = 0; // 子序列的数值之和 int subLength = 0; // 子序列的长度 for (int i = 0; i <= nums.length - 1; i++) { // 设置子序列起点为i sum = 0; for (int j = i; j <= nums.length - 1; j++) { //设置子序列终止位置为j sum += nums[j]; if(sum >= target){ // 一旦发现子序列和超过了target,更新result subLength = j - i + 1; // 取子序列的长度 result = result < subLength ? result :subLength; break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break } } } // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列 return result == Integer.MAX_VALUE ? 0 : result; } } //该题用暴力解法已经超出时间限制
-
双指针解法:一个for循环,里面的j指针指的是终止位置,滑动窗口的精髓就是如何移动起始位置
//时间复杂度:O(n) //空间复杂度:O(1) class Solution { public int minSubArrayLen(int target, int[] nums) { int i = 0;//起始位置 int sum = 0;// 滑动窗口数值之和 int result = Integer.MAX_VALUE;//取最大可以不断更新subLength int subLength = 0;// 滑动窗口的长度 for (int j = 0; j <= nums.length - 1; j++) { sum += nums[j]; while(sum >= target){ subLength = j - i + 1;// 取子序列的长度 result = result < subLength ? result :subLength; sum = sum - nums[i];// 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置) i++; } } // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列 return result == Integer.MAX_VALUE ? 0 : result; } }
-
-
总结
-
在本题中实现滑动窗口,主要确定如下三点:
- 窗口是什么?
- 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
- 如何移动窗口的起始位置?
- 窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。
- 如何移动窗口的结束位置?
- 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
- 窗口是什么?
-
解题的关键在于 窗口的起始位置如何移动,如图所示:result指的是最终我们要取的最小的长度
- 可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
-
1.2螺旋矩阵II
-
题目链接:https://leetcode.cn/problems/spiral-matrix-ii/
-
视频讲解:https://www.bilibili.com/video/BV1SL4y1N7mV/
-
文档讲解:https://programmercarl.com/0059.%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5II.html
-
解题思路:左闭右开 + 循环遍历
-
代码
//时间复杂度 O(n^2): 模拟遍历二维矩阵的时间 //空间复杂度 O(1) class Solution { public int[][] generateMatrix(int n) { int startx = 0;//起始行坐标 int starty = 0;//起始列坐标 int offset = 1;// // 需要控制每一条边遍历的长度,每次循环右边界收缩一位 int count = 1;// 矩阵中需要填写的数字 int loop = 1;// 记录当前的圈数 int nums[][] = new int[n][n]; int i,j; while(loop <= n/2){ for(j = starty;j < n - offset;j++){ nums[startx][j] = count++; } for(i = startx;i < n - offset;i++){ nums[i][j] = count++; } for(;j > starty;j--){ nums[i][j] = count++; } for(;i > startx;i--){ nums[i][j] = count++; } startx++; starty++; offset++; loop++; } // n 为奇数时,单独处理矩阵中心的值 if(n % 2 == 1){ nums[startx][starty] = count; } return nums; } }
-
总结
-
坚持循环不变量原则
-
模拟顺时针画矩阵的过程:
-
填充上行从左到右
-
填充右列从上到下
-
填充下行从右到左
-
填充左列从下到上
-
由外向内一圈一圈这么画下去,这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
-
按照左闭右开的原则画一圈:
- 这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
- 转圈数等于 n/2
-
-
2.区间和
-
题目
-
解题思路:
-
最直观的想法:给一个区间,然后 把这个区间的和都累加一遍。(这么做会超时)
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]; // 读取数组元素 for (int i = 0; i < n; i++) { vec[i] = scanner.nextInt(); } // 读取并处理查询 while (scanner.hasNextInt()) { int a = scanner.nextInt(); int b = scanner.nextInt(); int sum = 0; // 累加区间 a 到 b 的和 for (int i = a; i <= b; i++) { sum += vec[i]; } System.out.println(sum); } scanner.close(); } }
-
接下来我们来引入前缀和,看看前缀和如何解决这个问题
-
前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。
-
前缀和 在涉及计算区间和的问题时非常有用!
-
如果,我们想统计,在vec数组上 下标 2 到下标 5 之间的累加和,那是不是就用 p[5] - p[1] 就可以了。
- 特别注意: 在使用前缀和求解的时候,要特别注意 求解区间。
- 如上图,如果我们要求 区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]。
-
代码
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]; 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(); } }
-
-
3.开发商购买土地(后面再补)
- 文档链接:https://www.programmercarl.com/kamacoder/0044.%E5%BC%80%E5%8F%91%E5%95%86%E8%B4%AD%E4%B9%B0%E5%9C%9F%E5%9C%B0.html
4.数组专题总结
-
数组是存放在连续内存空间上的相同类型数据的集合。
-
数组下标都是从0开始的。
-
数组内存空间的地址是连续的
-
数组的元素是不能删的,只能覆盖。
-
数组的经典题目
-
二分法:循环不变量原则
- 暴力解法时间复杂度:O(n)
- 二分法时间复杂度:O(logn)
-
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
- 暴力解法时间复杂度:O(n^2)
- 双指针时间复杂度:O(n)
-
滑动窗口:滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
-
主要要理解滑动窗口如何移动 窗口起始位置,达到动态更新窗口大小的,从而得出长度最小的符合条件的长度。
-
暴力解法时间复杂度:O(n^2)
-
滑动窗口时间复杂度:O(n)
-
-
螺旋矩阵II这种模拟类题目用到了:循环不变量原则
-
通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。**
- 暴力解法时间复杂度:O(n^2)
- 双指针时间复杂度:O(n)
-
滑动窗口:滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
-
主要要理解滑动窗口如何移动 窗口起始位置,达到动态更新窗口大小的,从而得出长度最小的符合条件的长度。
-
暴力解法时间复杂度:O(n^2)
-
滑动窗口时间复杂度:O(n)
-
-
螺旋矩阵II这种模拟类题目用到了:循环不变量原则
-
前缀和:前缀和的思路很简单,但非常实用