单调队列可以记录子数组的最值,次最值,等等
子数组的和可以转换为前缀和的差
子数组批量增删可以转化为差分数组的边界调整
二分法要考虑边界外的元素具备什么性质,具体看灵神视频教程。
1. 数组类问题
技巧,
1.1. 枚举右维护左
为什么?枚举右,在往右遍历的过程中,左边的信息必然已经遍历过,因此可以方便的对左边进行一些计算或记录,再结合左右关系求解问题。
典型例题(灵神题单)
- 两数之和
- 好数对的数目 1161 相当于两数之差等于 0
- 与对应负数同时存在的最大正整数 1168 相当于两数之和等于 0
- 买卖股票的最佳时机
- ……
1.2. 滑动窗口(双指针)
求和为target的子数组,我之前的写法是这样的:
int cur_sum = 0;
int gap = 0;
for (int i = 0, j = 0; i <= j && j <= nums.size();) {
if (cur_sum < target) cur_sum += nums[j++];
else if (cur_sum > target) cur_sum -= nums[i++];
else {
gap = max(gap, j - i);
cur_sum -= nums[i++];
}
}这样写的问题在于判断条件多,且边界容易越界。
- 遍历完之后,还要再进去一次,不然会漏更新答案。所以循环条件要设为
j <= nums.size(),但这又容易带来越界的问题。 - 内部 i 可能超过 j.
滑窗的推荐写法应该如下,
int gap = 0, sum = 0;
for (int left = 0, right = 0; right < nums.size(); ++right) { // 外层扩张右边界
sum += nums[right];
while (sum > target) {
sum -= nums[left++]; // 内层收缩左边界
}
if (sum == target) {
gap = max(gap, right - left + 1);
}
}虽然看起来两个循环,但从元素遍历的角度来思考,每个元素至多遍历一次,时间复杂度仍然是 . 这样做的好处是,条件判断简单,边界清晰,不容易出错。