算法入门(一):滑动窗口 之 可变窗口-求最短 / 最长-数值计算 (Leetcode 209 / 713 / 2875 / 1004 / 2024)
- [可变窗口-求最短 / 最长](#可变窗口-求最短 / 最长)
- [模板 / 思维方式](#模板 / 思维方式)
- 数值计算
-
- [Leetcode 209](#Leetcode 209)
- [Leetcode 713](#Leetcode 713)
- [Leetcode 2875](#Leetcode 2875)
- [Leetcode1004 / 2024](#Leetcode1004 / 2024)
可变窗口-求最短 / 最长
可变窗口的核心是维护一个始终满足条件的窗口:
- 扩展:一般来说都是向右,将
最右侧的元素加入窗口做一些动作 - 收缩:到条件被破坏时,将
最左侧的元素移除,直到条件重新满足 - 统计:
统计一些窗口的数据,例如窗口长度 - 这类题目非常灵活,没有固定
代码模板,只有思维模板!
题目分两种类型: 纯数值的简单情况字符串/哈希表/队列/栈/堆的复杂情况。

模板 / 思维方式
cpp
// 思维方式(理解这个逻辑)
int left = 0;
for (int right = 0; right < n; right++) {
// 1. 扩展窗口:加入新元素
窗口加入 nums[right];
// 2. 收缩窗口:直到窗口重新满足条件
while (窗口不满足条件 && left <= right) {
窗口移除 nums[left];
left++;
}
// 3. 统计:此时窗口一定满足条件
if(条件满足){
统计当前窗口;
}
}
数值计算
Leetcode 209
该题没法使用模板,明确2 移除元素的动作 和3 统计量就可以了。
cpp
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int minLen = INT_MAX;
int windowSum = 0;
int left = 0;
int right = 0;
for(;right<n;right++){
windowSum += nums[right];
while(windowSum >= target){ //满足条件
minLen = min(minLen,right-left+1);//更新结果
windowSum -=nums[left];//更新窗口
left ++;//更新边界
}
}
return minLen <= n? minLen : 0;
}
Leetcode 713
遵循扩展,条件,统计的思考方式:
- 扩展: product *=numsright;
- 条件:当积大于等于k的时候,窗口需要收缩;
- 统计:当积严格小于k的时候,统计该次right存在时的满足条件的长度,累加起来。
cpp
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
int n = nums.size();
long long product = 1;
int left = 0;
int right = 0;
int res = 0;
for(; right <n ;right ++){
product *=nums[right];
while(product >= k && left <= right){
product /= nums[left];
left++;
}
res +=right -left +1;
}
return res;
}
Leetcode 2875
Leetcode209的扩展题。
一般会考虑两轮就覆盖所有情况,于是数组下标采取 % n 取mod:
cpp
class Solution {
public:
int minSizeSubarray(vector<int>& nums, int target) {
int n = nums.size();
int sum = 0;
int left = 0;
int right = 0;
int res = INT_MAX;
for(;right <n * 2;right++){
sum +=nums[right % n];
while(left<=right && sum > target){
sum -= nums [left % n];
left++;
}
if(sum == target) res = min(res,right-left+1);
}
return res == INT_MAX ? -1 : res;
}
};
可惜只能通过 80 %的用例。
如果情况是1,2,target = 7,1,2,1,2,1,2,1,2...,需要大于两组的和才能得到targer ,所以不能只在 2n 范围内搜索。
接下来要做出修改:
- 判断条件:是否等于 target 变更为 target % total
- 答案:res需要加上 target / total * n ,即多出来的组数 x 每一组的数量 。
cpp
class Solution {
public:
int minSizeSubarray(vector<int>& nums, int target) {
//1. 计算总和
long long total = 0;
for(int num: nums) total += num;
//2. 滑动窗口,判断条件全部变为取模
int n = nums.size();
int sum = 0;
int left = 0;
int right = 0;
int res = INT_MAX;
for(;right <n * 2;right++){
sum +=nums[right % n];
while(left<=right && sum > target % total){
sum -= nums [left % n];
left++;
}
if(sum == target % total) res = min(res,right-left+1);
}
//3. 结果记得也加上取模的变化
return res == INT_MAX ? -1 : res + target / total * n;
}
};
Leetcode1004 / 2024
Leetcode1004 / 2024 是同一种题目: nums\[\]只有两种元素的情况下,如何使用滑动窗口。