209.长度最小的子数组
题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/
视频讲解: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#思路
状态:只会暴力解法,前缀和思路卡住了
思路
把每一行(列)的总和计算出来,按行(列)切分,就是行(列)的前缀和求区间最大值
注意事项
- 同上一题的前缀和注意点
- 因为是二分,所以必定是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.前缀和【不会】:空间换时间的思想,记录至当前位置的前缀和,注意减的时候不要减多