滑动窗口算法详解
一. 算法框架
滑动窗口算法:
int left =0,right =0;
while(right<s.size()){
//增大窗口
window.add(s[right]);
right++;
while(window needs shrink){
//缩小窗口
window.remove(s[left]);
left++;
}
}
其实对应的算法思路很简单,主要是细节比较繁琐。 比如如何向窗口添加新元素,如何缩小窗口,在窗口滑动的哪个阶段更新结果。
滑动窗口套路框架:
void slidingWindow(String s,String t){
Map<char,Integer> need = new HashMap<>();
Map<char,Integer> window = new HashMap<>();
for(char c:t) need.put(c,map.getOrDefault(c,0)+1);
int left =0,right =0;
int valid =0;
while (right<s.size()){
//c是将移入窗口的字符
char c = s[right];
//右移窗口
right++;
//进行窗口内数据的一系列更新
...
//debug输出的位置
System.out.println("window:[%d,%d)\n",left,right);
//判断左侧窗口是否要收缩
while(window needs shrink){
//d是将移出窗口的字符
char d = s[left];
left++;
//进行窗口数据的一系列更新;
}
}
}
二.最小覆盖子串
滑动窗口算法的思路实际上是这样的:
- 使用双指针中的左右指针技巧,初始化left=right=0,把[left,right)称为一个窗口。
- 先不断增大right,扩大这个窗口,直到窗口满足我们所需的条件(这道题中即,包含了T所有字符)
- 再不断增大left,缩小这个窗口,使得我们在保证这个窗口满足条件的同时,尽可能的小。直到窗口内的内容不满足条件,跳出(即不包含T中所有字符了)。每次更新left的时候,也要对最终的结果进行一次更新。
- 重复第2和3步,直到right达到了字符串s的尾部。
实际上,增大right是在找可行解,找到可行解后,通过增大left来找最优解(即优化)。
在框架中使用了needs和window字典,他们充当的是计数器的功能。needs用来记录T中每个字符出现的次数,即:我们所需要的字符的情况;window字典则是用来记录当前实际窗口中包含对应字符的情况,通过两个map的对比,来判断是否满足了条件。
回到这道题:
首先,我们初始化needs和window两个哈希表,记录需要凑齐的字符和当前窗口中的字符情况。
String minstr(String s,String t){
Map<Char,Integer> needs = new HashMap<>();
Map<Char,Integer> window = new HashMap<>();
for(Char c:t) needs.put(c,map.getOrDefault(c,0)+1);
然后,使用left和right变量初始化窗口两端,区间为左闭右开,这样保证了一开始窗口中没有任何元素。
int left=0,right=0;
int valid =0;
while (right<s.size()){
//开始滑动
}
其中,valid变量表示已经满足条件的字符的个数,如果valid和need中key的数量相等(needs.size()),则说明窗口已满足条件,完全覆盖了T。
接下来需要考虑的问题是:
- 扩大窗口时,应该更新哪些数据?
- 什么条件下,窗口应暂停扩大,开始移动left? vaild = needs.size()
- 缩小窗口时,即移出字符时,应更新哪些数据?
- 需要的结果,应该在什么时候更新?应该在缩小窗口时。
完整代码,但是存在很多细节的小问题,后面会一一解答:
String minstr(String s,String t