模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;

int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
...

// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}

最长不含重复字符的子字符串

image-20220318114036302

朴素的想法:

无非就是双指针,一个指向前,一个指向后,用一个hash表记录碰到的字符出现的次数,如果字符出现的次数变为了2,那么左指针就要一直移动直到消除这个碰到的字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char,int>window;
int left = 0,right = 0;
int maxl = 0;

while(right < s.length()){
char c = s[right];
window[c]++;
right++;
while(window[c]>1){//need shrink
window[s[left]]--;
left++;
}
maxl = max(maxl,right-left);
}
return maxl;
}
};

最小覆盖子串

image-20220325201749969

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
string minWindow(string s, string t) {
unordered_map<char,int>need,window;
//window里存储t中字符数量
for(char c: t)need[c]++;

int left = 0,right = 0;
int needCnt = need.size();
int minl = INT_MAX,start = 0;
while(right < s.length()){
char c = s[right];
right++;

if(need.count(c) > 0){
window[c]++;
if(window[c] == need[c]){
needCnt--;
}
}

//需要收缩
while(needCnt == 0){
if(minl > right - left){
start = left;
minl = right - left;
}
char d = s[left];
left++;
if(need.count(d) > 0){
if(window[d] == need[d])
needCnt++;
window[d]--;
}
}
}
return minl == INT_MAX ? "" : s.substr(start,minl);
}

字符串的排列

image-20220326110747784

window滑动窗口的双指针更新操作如下:

  • right指针一直加1
  • 如果碰到了需要的字符(s[right]在need子串之中),那么更新window窗口数组(window[s[right]]++且判断是否needCnt--
  • 如果window窗口数组满足条件,left++直到window不再满足条件(window[s[left]]--且判断是否needCnt++,期间可以更新最短的覆盖子串的长度等各种信息)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char,int>need,window;
for(char c : s1)need[c]++;
int needCnt = need.size();

int left = 0,right = 0;
while(right < s2.length()){
char c = s2[right];
right++;

if(need.count(c) > 0){
window[c]++;
if(window[c] == need[c])
needCnt--;
}

while(needCnt == 0){
if(right-left == s1.size())return true;

char d = s2[left];
left++;
if(need.count(d) > 0){
window[d]--;
if(window[d]<need[d])needCnt++;
}
}
}
return false;
}
};

至多包含两个不同字符串的最长子串

image-20220327111557637

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
int lengthOfLongestSubstringTwoDistinct(string s) {
unordered_map<char,int>windows;

int left = 0,right = 0;
int cnt = 0;
int maxl = 0;
while(right < s.length()){
char c = s[right];
right++;

if(windows.count(c) == 0||windows[c] == 0)
cnt++;
windows[c]++;

while(cnt == 3){
char d = s[left];
windows[d]--;
if(windows[d] == 0)cnt--;
left++;
}
if(cnt <= 2)maxl = max(maxl,right - left);
}
return maxl;
}
};

找到字符串中所有的字母异位词

image-20220327113858618

window滑动窗口的双指针更新操作如下:

  • right指针一直加1
  • 如果碰到了需要的字符(s[right]在need子串之中),那么更新window窗口数组(window[s[right]]++且判断是否needCnt--
    注意判断是否needCnt–最稳妥的方法就是window[c] == need[c]的时候,即务必使用==
  • 如果window窗口数组满足条件,left++直到window不再满足条件(window[s[left]]--且判断是否needCnt++,期间可以更新最短的覆盖子串的长度等各种信息)
    注意判断是否needCnt++最稳妥的方法就是window[d] == need[d]的时候,即务必使用==

总而言之,滑动窗口题目的样式十分固定,仅需注意一些细节以及更新结果的位置,结果更新的位置可能在while循环前面可能在window满足条件时更新,也可能在最后更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char,int>need,window;
for(auto e : p)need[e]++;
int needCnt = need.size();

int left = 0,right = 0;
vector<int>results;
while(right < s.size()){
char c = s[right];
right++;

if(need.count(c)){
window[c]++;
if(window[c] == need[c])
needCnt--;
}

while(right - left > p.size()){
char d = s[left];
if(need.count(d)){
if(window[d] == need[d])needCnt++;
window[d]--;
}
left++;
}
if(needCnt == 0)results.push_back(left);
}
return results;

}
};

子数组的最大平均数

image-20220327120807645

window滑动窗口的双指针更新操作如下:

  • right指针一直加1
  • 每次sum的值+nums[right]
  • 如果window窗口数组过于大了需要收缩,left++直到window再次满足条件sum的值每次-nums[left]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
int left = 0,right = 0;
double maxd = -100000.0;
double sum = 0.0;
while(right < nums.size()){

double c = nums[right];
right++;
sum += c;
if(right - left == k)maxd = max(maxd,sum/k);

while(right - left >= k){
double d = nums[left];
sum -= d;
left++;
}


}
return maxd;
}
};

滑动窗口最大值

image-20220327145908481

队列按从大到小放入

  • 如果首位值(即最大值)不在窗口区间,删除首位
  • 如果新增的值小于队列尾部值,加到队列尾部
  • 如果新增值大于队列尾部值,删除队列中比新增值小的值,如果在把新增值加入到队列中
  • 如果新增值大于队列中所有值,删除所有,然后把新增值放到队列首位,保证队列一直是从大到小

队列移除:

  • 如果移除的值恰好为队列首部元素,删除队列首部元素即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int left = 0,right = 0;
deque<int>dq;
vector<int>results;

while(right < nums.size()){
int c = nums[right];
right++;

while(!dq.empty()&&dq.back() < c){
dq.pop_back();
}
dq.push_back(c);

if(right - left == k){
int d = nums[left];
results.push_back(dq[0]);
if(dq.front() == d)
dq.pop_front();
left++;
}

}
return results;
}
};