LeetCode 209 长度最小的子数组
思路
滑动窗口
- 不断调整子序列的起始和终止位置,是一种特殊的双指针
- 实现滑动窗口
- 窗口内要保持什么:总和大于等于
target的长度最小的 连续子数组 - 如何移动窗口起始位置:当已满足上述条件,则窗口该缩小了,起始位置+1
- 如何移动窗口结束位置:当不满足上述条件,则窗口该变大了,结束位置+1
- 窗口内要保持什么:总和大于等于
- 时间复杂度
- 移动起始位置和移动结束位置仍需要两层循环
- 但因为每个数组中的元素都只被操作两次(进窗口,出窗口),所以时间复杂度为
- 滑动窗口本质上是一种不完全的遍历,适用于要找满足某条件的某子序列时,对遍历到的每个序列记录与条件相关的属性
难点
如何处理最小长度的记录:
- 在从内部循环跳出后需要更新最小长度记录,但是此时的子序列不一定满足题设条件,因为可能窗口右侧已经超出。所以仍需判断sum是否大于target
- 最小长度的初始值应该设置为比数组长度更大的值,推荐
Integer.MAX_VALUE。因为有一种情况是最小长度确实是数组长度,如果初始值也是数组长度,会无法和找不到满足条件的子序列做区分
LeetCode 59 螺旋矩阵2️⃣
思路
既然考察对代码的控制能力,就要选定一个统一的边界: 对于边长为n的正方形,每条边处理n-1个元素,这样处理四次正好处理完最外面一圈 整体思路:
- 先初始化一个形状确定的矩阵
- 每次处理最外圈后,边长-2
- 如果边长迭代为为0或1,单独处理
- 0:直接返回
- 1:处理中心元素后返回
难点
在每次遍历完最外圈后,指针会回到此圈的起始位置。此时需要把行列索引都+1,才能继续下一圈的遍历
卡码网 58 区间和
题目链接:58区间和
思路
首先想到暴力:对每一对查询都依次叠加元素求和 但这样对大查询次数,时间复杂度很大,且有多次重复计算 如何避免重复计算?👉前缀和
前缀和
前缀和常用于计算区间和 大致思路:
- 统计此数组每个索引对应的前缀和
- 对于每个区间查询,只需要用区间右索引的前缀和减去区间左索引前序的前缀和
难点
由于输入的区间是闭区间,做前缀和相减时要考虑索引是否有效
代码
import java.util.Scanner;
public class Main{
public static void main (String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] arr = new int[n];
int[] presum = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = scanner.nextInt();
if (i == 0) {
presum[i] = arr[i];
}
else {
presum[i] = presum[i-1] + arr[i];
}
}
while (scanner.hasNext()) {
int i = scanner.nextInt();
int j = scanner.nextInt();
int sum;
if (i > 0) {
sum = presum[j] - presum[i-1];
}
else {
sum = presum[j];
}
System.out.println(sum);
}
}
}
卡码网 44 开发商购买土地
思路
第一反应是暴力解法:横向遍历每种解法,纵向遍历每种解法,找到价差最小的 结合上一题的前缀和,可以减少在暴力解法中重复计算 大致思路:
- 对横向和纵向分别计算前缀和数组
- 遍历每种分割,对每种分割求解价差,保留最小值
难点
尽管这道题思路挺清晰,但在求和的时候也比较考验代码的控制能力。 计算前缀和时我分为了两步:
- 读入输入时顺便计算每行每列的和存储在数组中
- 全部读完后把行列和依次累加,得到需要的两个前缀和数组
代码
import java.util.*;
public class Main {
public static void main (String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[][] matrix = new int[n][m];
int[] presumHrz = new int[n]; // 先行和
int[] presumVtc = new int[m]; // 先列和
for (int i = 0; i < n; i++) {
// 先记录每行列和
for (int j = 0;j < m ; j++) {
matrix[i][j] = scanner.nextInt();
presumHrz[i] += matrix[i][j];
presumVtc[j] += matrix[i][j];
}
}
// 根据行列和计算前缀和
// 计算水平分割的前缀和
for (int i = 1; i < n; i++) {
presumHrz[i] += presumHrz[i-1];
}
// 计算竖直分割的前缀和
for (int i = 1; i < m; i++) {
presumVtc[i] += presumVtc[i-1];
}
int minDiff = Integer.MAX_VALUE;
// 水平分割
for (int i = 0; i < n-1; i++) {
int diff = Math.abs(presumHrz[i] * 2 - presumHrz[n-1]);
if (diff < minDiff) {
minDiff = diff;
}
}
// 竖直分割
for (int i = 0; i < m-1; i++) {
int diff = Math.abs(presumVtc[i] * 2 - presumVtc[m-1]);
if (diff < minDiff) {
minDiff = diff;
}
}
System.out.println(minDiff);
}
数组专题总结
今日收获总结
今天算法学习三小时,滑动窗口是复习知识点,边界条件的处理需要注意。前缀和是第一次学习,也是求子序列和的常用方法。此外卡码网的算法题也督促我复习了ACM输入输出模式。