滑动窗口题总结

滑动窗口算法

这个算法技巧的思路非常简单,就是维护一个窗口,然后不断的滑动,然后更新答案。

大概的思路模板

1
2
3
4
5
6
7
8
9
10
11
12
13
int left = 0, right = 0;

while (right < s.size()) {
// 增大窗口
window.add(s[right]);
right++;

while (window needs shrink) {
// 缩小窗口
window.remove(s[left]);
left++;
}
}

算法的时间复杂度为O(N),比字符串暴力算法高效很多。

通用的滑动窗口算法框架

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
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++;
// 进行窗口内数据的一系列更新
...

/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/

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

滑动窗口的思路

  1. 我们在字符串S中使用双指针中的左右指针技巧,初始化left = right = 0,把索引左闭右开区间[left , right)称为一个窗口。

  2. 我们先不断地增加right指针扩大窗口[left , right),直到窗口中的字符串符合要求(包含了T中的所有字符)

  3. 此时,我们停止增加right,转而不断增加left指针缩小窗口[left , right),直到窗口中的字符串不再符合要求。同时,每次增加left,我们都要更新一轮结果。

  4. 重复第2和第3步,直到right到达字符串S的尽头。

主要思路:第2步相当于在寻找一个可行解,然后第3步在优化这个可行解,最终找到最优解。

needs和window相当于计数器,分别记录T中字符出现次数和窗口中字符出现次数。

可以清晰的看到这个过程:

  1. 初始状态:

image-20211206211954222

  1. 增加right,直到窗口[left , right)包含了T的所有字符。

image-20211206212312429

  1. 增加left,缩小窗口[left , right]:

    image-20211206212349932

直到窗口中的字符串不再符合要求,left不再继续移动:

image-20211206212433460

之后重复上述过程,先移动right,再移动left… 直到right指针到达字符串S的末端,算法结束。