LeetCode 209
思路
- 滑动窗口法,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
- 为避免与暴力算法相似,我们将把j看作终点进行遍历以实现仅需要循环一次的目的
- 在j循环的过程中,我们需要把每一次的数组值加起来以判断此时该集合内所有数的值是否与target相等或者大于target
- 若满足上述条件,我们就移动起始位置i,缩小集合长度并判断此时是否依旧满足,直至不满足条件
- 重复上述步骤后我们得到的res就是我们能要找的最小的长度
注意点
- res的初始值应该是比较大的一开始,因为我们每次是取更小的那个值,如果res一开始就很小那么就无法更新它的值
- 我们要单独判断一下没有找到所需集合这个情况
- 当判断sum >= target时是使用while语句而非if,我们举个例子,如果当前数组为[1, 1, 1, 100],target = 100,那么如果我们使用的语句为if,第一次满足条件的情况为[0, 3]区间集合,sum = 103,但其实我们轻松地就可以知道,其实[2, 3]区间集合才是最小的,因为此时sum = 101,而由于我们使用的语句是if,我们在第一次满足条件时循环就结束了
代码
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//滑动窗口法
int i = 0; //起始点
int sum = 0;
int res = Integer.MAX_VALUE; //初始化res的值
for (int j = 0; j < nums.length; j++) {
sum += nums[j]; //计算当前区间集合内所有数的和
while (sum >= target) {
int subL = j - i + 1; //满足条件后进入循环,计算出当前区间集合长度
res = Math.min(res, subL);
sum -= nums[i]; //注意!!一定要记得减去区间起点
i++;
}
}
if (i != 0) //判断没有要求区间的情况
return res;
return 0;
}
}
LeetCode 59
思路
-
统一区间为左闭右开
-
分别对正方形的四边遍历放值
-
内缩圈后重复上述操作
-
图示
注意点
- 由于我们知道圈数(即循环次数)是由n /= 2得到的,那么如果n为奇数,我们实际循环次数就少了一次,最中心的元素我们无法放入数组中,因此我们在最后还要单独再判断一次
- 我们不能固定以0作为开始,因为若数组比较大,那么缩圈之后每一次的开始就不是0了,因此我们单独规定了一个startx和starty
代码
class Solution {
public int[][] generateMatrix(int n) {
//区间左闭右开
int[][] res = new int[n][n];
int i = 0;
int j = 0;
int startx = 0;
int starty = 0;
int offset = 1;
int count = 1;
int cm = n / 2;
while(cm > 0) {
for(j = starty; j < n-offset; j++) //第一横行
res[starty][j] = count++;
for(i = startx; i < n-offset; i++) //第一纵列
res[i][j] = count++;
for(; j > starty; j--) //第二横行
res[i][j] = count++;
for(; i > startx; i--) //第二纵列
res[i][j] = count++;
startx++;
starty++;
offset++;
cm--;
}
if(n % 2 == 1) { //奇数情况赋值
res[startx][starty] = count;
}
return res;
}
}
开发商买土地
思路
- 看成求两个区间和
- 使用求出横向和,纵向和
- 使用前缀和分别模拟横向切割情况和纵向切割情况
注意点
- 减去的是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 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();
}
}
优化代码
我们可以不使用前缀和,而直接在行向遍历的时候,遇到行末尾就统一一下, 在列向遍历的时候,遇到列末尾就统计一下。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int m = scan.nextInt();
int sum = 0;
int[][] field = new int[n][m];
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
field[i][j] = scan.nextInt();
sum += field[i][j];
}
}
//horizantal_cut
int horizantal = 0;
int res = Integer.MAX_VALUE;
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
horizantal += field[i][j];
if(j == m-1) {
res = Math.min(res, Math.abs(sum - horizantal - horizantal));
}
}
}
//vertical_cut
int vertical = 0;
for(int j = 0; j < m; j++) {
for(int i = 0; i < n; i++) {
vertical += field[i][j];
if(i == n-1) {
res = Math.min(res, Math.abs(sum - vertical - vertical));
}
}
}
System.out.println(res);
scan.close();
}
}
总结
今天的内容主要是前缀和,滑动窗口,在写开发商买土地的时候花了比较多的时间,是一道很好的利用了区间和的题,对于区间和有了更深刻的理解。