思想
相当于在整个数组和字符串上用窗口圈出部分符合题意的数组和字符串,同时这个窗口是滑动的,
- 可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“ 请找到满足 xx 的最 x 的区间(子串、子数组)的 xx ”这类问题都可以使用该方法进行解决。
关键点
- 主要应用在数组和字符串上
- 左右双指针:没有重复字母时调整右边界;有重复字母时调整左边界
- 适用范围:
1.一般给出的数据结构是数组或者字符串
2.求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
3.固定窗口大小;窗口大小不固定,求解最大的满足条件的窗口;窗口大小不固定,求解最小的满足条件的窗口
滑动窗口题目(力扣)
滑动窗口题目:
-
无重复字符的最长子串
-
串联所有单词的子串
-
最小覆盖子串
-
至多包含两个不同字符的最长子串
-
长度最小的子数组
-
滑动窗口最大值
-
字符串的排列
-
最小区间
-
最小窗口子序列
代码模板
滑动窗口模板1或2
void slidingWindow(string s, string t) {
//第一步,need表示需要匹配的字符,window表示匹配的情况
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
//第二步
while (right < s.size()){
char c = s[right];
right++;
// 进行窗口内数据的一系列更新
...
//第三步
while (window needs shrink) {
//判断子串
char d = s[left];
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
// 滑动窗口模板
for () {
// 将新进来的右边的数据,计算进来
// 更新数据
// 判断窗口数据是否不满足要求了
while (窗口数据不满要求 && left < arrSize) {
// 移除left数据,更新窗口数据
left++;
}
right++;
}
例题:
第一题:3.无重复字符的最长子串
大佬解法:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if(s.size() == 0) return 0;
unordered_set<char> lookup; //因为无重复字符,所以要用unordered_set
//注意这里是unordered_set,不是unordered_map
int maxStr = 0;
int left = 0;
for(int i = 0; i < s.size(); i++){
while (lookup.find(s[i]) != lookup.end()){
//如果没找到,会返回unordered_map::end
//此处说明有重复
lookup.erase(s[left]);
left ++;
}
maxStr = max(maxStr,i-left+1);
//while外是满足条件的,在这里更新
lookup.insert(s[i]);
}
return maxStr;
}
};
第二题:209. 长度最小的子数组
我的解法:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
if(nums.size()<1)
return 0;
int len=1e7;
int left=0;
int sum=0;
for(int right=0;right<nums.size();right++)
{
sum+=nums[right];
while(sum>=target)
{
len=min(len,right-left+1);
//while内是满足条件的,在这里更新
sum-=nums[left]; //满足条件的
left++; //left是最后更新!
}
}
return len==1e7?0:len;
}
};
自我梳理模板流程
对于无重复最大/最小:
- 判断字符串/数组是否为空
- 初始化maxnum或minnum=0、左边界为0、unordered_set(存储子串)
- for循环遍历字符串/字符:
判断加入右边的新字符后是否依旧满足:
1)不满足则在unordered_set去掉(erase函数)左边字母;同时left++;注意这里是while函数,不是if函数
2)(无论满不满足),将新字符添加到子串中,然后更新maxnum或minnum,此时是将原来的maxnum与新窗口大小**(i-left+1)**作比较得到。
坑
560. 和为 K 的子数组不能用滑动窗口做,因为nums[i]可以为负数,也就是说右指针i向后移1位不能保证区间会增大,左指针j向后移1位也不能保证区间和会减小,即不知道是往右收缩,还是往左收缩。
该题评论选取:
209. 长度最小的子数组 - 力扣(LeetCode) 全是正数的,能用滑动窗口做。
210. 和为 K 的子数组 - 力扣(LeetCode) 含有负数的,用前缀和+哈希表转换成 1. 两数之和 - 力扣(LeetCode) 的解法。
211. 862. 和至少为 K 的最短子数组 - 力扣(LeetCode) 和 lc 560 一样,含有负数,用单调双端队列将窗口的数据转成单调的数据,进而用滑动窗口的思路解决。 当然 lc 560 也可以用前缀和+单调双端队列进行求解。