滑动窗口类型题目总结
1. 滑动窗口应用场景
- 子数组子串问题:
- 条件单调性场景,窗口扩大使得满足题目条件的概率增加,窗口减小必然使得题目条件概率减小。
第二点是关键,如果不满足,则无法使用滑动窗口方法来解决题目。
2. 基本框架
这里采用的框架是labuladong的滑动窗口框架,原因是简单好用,用的熟悉。
int left = 0;
int right = 0; // 左右边界
while (right < s.size()) { // 滑动窗口的范围是[left, right),
// 增大窗口
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩小窗口
window.remove(s[left]);
left++;
}
框架的使用需要考虑几个问题:
- 窗口什么时候增大,增大需要修改哪些状态
- 窗口什么时候减小,减小需要修改哪些状态
- 什么时候窗口满足条件,如何记录满足条件的结果
3. 例题应用
- 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
分析
首先,子串问题,可以考虑使用滑动窗口。第二,考虑题目的条件,是涵盖t的所有字符,可以想到,当窗口越大的时候,越有可能包含t中所有字符的子串;结合这两点,可以考虑用滑动窗口的方法解决问题。
在决定使用滑动窗口后,我们来思考框架使用的几个问题。
-
窗口在什么时候增大,增大需要修改哪些状态?
答:当窗口没有覆盖t所有字符的时候,需要增大;增大的时候需要修改的变量有,窗口内的字符计数,已经满足t的数量 -
窗口在什么时候需要减小,减小需要修改哪些状态?
答:当窗口已经满足条件的时候,可以减小,增大的时候需要修改的变量有,窗口内的字符计数,已经满足t的数量,直到窗口不满足条件为止。 -
什么时候窗口满足条件,如何记录满足条件的结果?
答:当valid的数等于记录窗口。
class Solution {
public String minWindow(String s, String t) {
// 建立窗口的记录和实际的记录
Map<Character, Integer> window = new HashMap<>();
Map<Character, Integer> need = new HashMap<>();
// 初始化
for(int i = 0; i < t.length(); i++){
char cur = t.charAt(i);
need.put(cur, need.getOrDefault(cur, 0) + 1);
}
int left = 0;
int right = 0;
int valid = 0;
String result = "";
int minLen = Integer.MAX_VALUE;
// 左闭右开的区间
while(right < s.length()){
char cur = s.charAt(right);
right++;
// 增加窗口字母数量
if(need.containsKey(cur)){
window.put(cur, window.getOrDefault(cur, 0) + 1);
if(window.get(cur).equals(need.get(cur))){
valid++;
}
// 优化, 移动左指针
while(left < right && valid == need.size()){
char d = s.charAt(left);
if(need.containsKey(d)){
if(window.get(d).equals(need.get(d))){
valid--;
if(right - left < minLen){
result = s.substring(left, right);
minLen = right - left;
}
}
// 更新窗口
window.put(d, window.get(d) - 1);
}
left++;
}
}
}
return result;
}
}
567. 字符串的排列
class Solution {
public boolean checkInclusion(String s1, String s2) {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
for(int i = 0; i < s1.length(); i++){
char cur = s1.charAt(i);
need.put(cur, need.getOrDefault(cur, 0) + 1);
}
int left = 0;
int right = 0;
int valid = 0;
int start = 0;
int len = Integer.MAX_VALUE;
while(right < s2.length()){
char cur = s2.charAt(right);
right++;
if(need.containsKey(cur)){
window.put(cur , window.getOrDefault(cur, 0) + 1);
if(window.get(cur).equals(need.get(cur))){
valid++;
}
}
while(right - left >= s1.length()){
// 更新左节点
if(valid == need.size()){
return true;
}
char d = s2.charAt(left);
left++;
if(need.containsKey(d)){
if(window.get(d).equals(need.get(d))){
valid--;
}
window.put(d, window.getOrDefault(d, 0) - 1);
}
}
}
return false;
}
}
3. 无重复字符的最长子串
分析
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> window = new HashMap<>();
int left = 0;
int right = 0;
int maxLen = 0;
while(right < s.length()){
char cur = s.charAt(right);
right++;
if(!window.containsKey(cur)){
window.put(cur, 1);
maxLen = Math.max(right - left, maxLen);
}
else{
while(left + 1< right){
char d = s.charAt(left);
if(window.containsKey(cur)){
window.remove(d);
}
else{
break;
}
left++;
}
window.put(cur, 1);
}
}
return maxLen;
}
}