目录
209. 长度最小的子数组
滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
使用前提条件:1. 要找数组中连续的子数组,2. 无序数组
暴力和滑动窗口的解题区别:
暴力是窗口的起点遍历去找满足条件的子数组(因为每个起点首次满足条件的都是当前起点的最小长度数组),滑动窗口是窗口的终点遍历去找最小的且满足条件的子数组(最后一次循环都是当前遍历里终点的最小长度数组)
想象成可滑动的窗口会更好理解
public class MinimumSizeSubarraySum {
/**
* 滑动窗口方法(一个for循环去取代两层for循环,遍历窗口的终止位置,联合while循环来找到最小子数组)
*
* @param target
* @param nums
* @return result 最小子数组长度
*/
public int slideWindow(int target, int[] nums) {
// 这里初始最大值是为了让所有满足条件的窗口长度都能作比较,来取得最小值
int result = Integer.MAX_VALUE;
int start = 0;
int sum = 0;
// 遍历窗口的终止位置
for (int end = 0; end < nums.length; end++) {
sum += nums[end];
// 和大于target值的,取其最小的数组长度
while (sum >= target) {
/* 精简前的代码
int subLength = end - start + 1;
result = result < subLength ? result : subLength;
// 起始位置后移
sum -= nums[start];
start++;*/
result = Math.min(result, end - start + 1);
sum -= nums[start++];
}
}
// 如果找不到满足的子数组,则result仍是初始值
result = result == Integer.MAX_VALUE ? 0 : result;
return result;
}
/**
* 暴力解法(两个for循环不断搜索符合条件的区间,外层for循环遍历窗口的起始位置,内层for则遍历窗口的终止位置)
*
* @param target
* @param nums
* @return
*/
public int bruteMethod(int target, int[] nums) {
int sum = 0;
int subLength = 0;
int result = Integer.MAX_VALUE;
for (int left = 0; left < nums.length; left++) {
// 新起点需要重新加和
sum = 0;
for (int right = left; right < nums.length; right++) {
sum += nums[right];
if (sum >= target) {
subLength = right - left + 1;
result = result < subLength ? result : subLength;
// 找到就跳出内层循环,因为是当前循环中最短的
break;
}
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
关于for和while:
for循环需要在开始前已知具体的迭代次数,while循环适用于执行前不确定循环次数的情况,适合需要动态调整的条件,较为灵活。
59.螺旋矩阵II
解题核心思路:循环不变量-这里的规则是左闭右开原则
模拟顺时针画矩阵:
找到矩阵中间的规律:
n | 1 | 2 | 3 | 4 | 5 |
矩阵中间坐标 | (0,0) | 无 | (1,1) | 无 | (2,2) |
矩阵中间的坐标和起始位置有关
public class SpiralMatrixII {
/**
* 二分法的核心-循环不变量
* 该题的不变量:每圈的边都是左闭右开,走n-缩进值
*
* @param n
* @return
*/
public int[][] generateMatrix(int n) {
int[][] nums = new int[n][n];
// 每圈的起始位置
int startx = 0, starty = 0;
// 圈数
int loop = 1;
int count = 1;
// 右缩进值,初始化第一圈为1,每走一圈再收缩一位
int offset = 1;
// 行,列
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++;
}
// 每走完一圈圈数、起始位置、右缩进值都加一
loop++;
startx++;
starty++;
offset++;
}
// 矩阵中心的值单独处理
if (n % 2 == 1) {
nums[startx][starty] = count;
}
return nums;
}
}
58.区间和
前缀和的思想:重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。
public class IntervalSum {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 行数
int n = scanner.nextInt();
// 数组
int[] array = new int[n];
// 存元素总和的数组
int[] sumArr = new int[n];
int preSum = 0;
for (int i = 0; i < n; i++) {
array[i] = scanner.nextInt();
preSum+=array[i];
// 存储下标为0到当前下标的元素总和
sumArr[i] = preSum;
}
while (scanner.hasNextInt()){
int startIndex = scanner.nextInt();
int endIndex = scanner.nextInt();
int sum;
if (startIndex == 0){
// 当区间起始下标为0时,直接输出终止下标对应的元素总和
sum = sumArr[endIndex];
}else {
// 否则需 终止下标对应的元素总和 - 起始下标前一位的元素总和
sum = sumArr[endIndex] - sumArr[startIndex-1];
}
System.out.println(sum);
}
scanner.close();
}
}