滑动窗口技巧
滑动窗口算法的技巧:
滑动窗口算法的思路:
- 我们在字符串
S中使用双指针中的左右指针技巧,初始化left = right = 0,把索引左闭右开区间[left, right)称为一个「窗口」。 - 不断地增加
right指针扩大窗口[left, right),直到窗口中的字符串符合要求(包含了T中的所有字符)。 - 此时,我们停止增加
right,转而不断增加left指针缩小窗口[left, right),直到窗口中的字符串不再符合要求(不包含T中的所有字符了)。同时,每次增加left,我们都要更新一轮结果。 - 重复第 2 和第 3 步,直到
right到达字符串S的尽头。
重点:
第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。
模板,只需要思考以下几个问题:
1、什么时候应该移动 right 扩大窗口?窗口加入字符时,应该更新哪些数据?
2、什么时候窗口应该暂停扩大,开始移动 left 缩小窗口?从窗口移出字符时,应该更新哪些数据?
3、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
如果一个字符进入窗口,应该增加 window 计数器;如果一个字符将移出窗口的时候,应该减少 window 计数器;当 valid 满足 need 时应该收缩窗口;应该在收缩窗口的时候更新最终结果
适用场景:
子数组/子串相关的问题
主要要记得:
1、什么时候应该扩大窗口?
2、什么时候应该缩小窗口?
3、什么时候应该更新答案?
76. 最小覆盖子串
题解:
public String minWindow(String s, String t) {
Map<Character, Integer> need = new HashMap<>();
for (char tmp:t.toCharArray()) {
need.put(tmp,need.getOrDefault(tmp,0)+1);
}
Map<Character,Integer> windows = new HashMap<>();
int left = 0, right = 0, valid = 0,start= 0,len = Integer.MAX_VALUE;
while (right < s.length()){
char a = s.charAt(right);
right++;
if(need.containsKey(a)){
windows.put(a,windows.getOrDefault(a,0)+1);
if(windows.get(a).equals(need.get(a)) ){
valid ++;
}
}
while(valid == need.size()){
if(right - left < len){
start = left;
len = right - left;
}
char b = s.charAt(left);
left ++;
if(need.containsKey(b)){
if(windows.get(b).equals(need.get(b))){
valid --;
}
windows.put(b,windows.getOrDefault(b,0)-1);
}
}
}
return len == Integer.MAX_VALUE ?"":s.substring(start,len+start);
}
567. 字符串的排列
题解:
public boolean checkInclusion(String s1, String s2){
Map<Character, Integer> window = new HashMap<>();
Map<Character, Integer> need = new HashMap<>();
for(char tmp : s1.toCharArray()){
need.put(tmp,need.getOrDefault(tmp,0)+1);
}
int left =0,right = 0,valid =0;
while (right < s2.length()) {
char a = s2.charAt(right);
right++;
if(need.containsKey(a)){
window.put(a,window.getOrDefault(a,0)+1);
if(need.get(a).equals(window.get(a))){
valid++;
}
}
// 缩小的条件。此时说明走的长度等于 需要比较的字符串的长度。因为有排列,所以需要长度想等。
while (right -left == s1.length()){
if(valid == need.size()){
return true;
}
char b = s2.charAt(left);
left++;
if(need.containsKey(b)){
if(window.get(b).equals(need.get(b))){
valid--;
}
window.put(b,window.getOrDefault(b,0)-1);
}
}
}
return false;
}
备注:
1、本题移动 left 缩小窗口的时机是窗口大小大于 t.size() 时,因为排列嘛,显然长度应该是一样的。
2、当发现 valid == need.size() 时,就说明窗口中就是一个合法的排列,所以返回 true。
438. 找到字符串中所有字母异位词
题解:
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result =new ArrayList<>();
Map<Character, Integer> window = new HashMap<>();
Map<Character, Integer> need = new HashMap<>();
for(char tmp : p.toCharArray()){
need.put(tmp,need.getOrDefault(tmp,0)+1);
}
int left =0,right = 0,valid =0;
while (right < s.length()) {
char a = s.charAt(right);
right++;
if(need.containsKey(a)){
window.put(a,window.getOrDefault(a,0)+1);
if(need.get(a).equals(window.get(a))){
valid++;
}
}
while (right -left >= p.length()){
if(valid == need.size()){
result.add(left);
}
char b = s.charAt(left);
left++;
if(need.containsKey(b)){
if(window.get(b).equals(need.get(b))){
valid--;
}
window.put(b,window.getOrDefault(b,0)-1);
}
}
}
return result;
}
3. 无重复字符的最长子串
题解:
public int lengthOfLongestSubstring(String s) {
Map<Character,Integer> windows = new HashMap<>();
int left =0,right =0,res=0;
while(right < s.length()){
char a = s.charAt(right);
right++;
windows.put(a,windows.getOrDefault(a,0)+1);
while(windows.get(a) > 1){
char b = s.charAt(left);
windows.put(b,windows.getOrDefault(b,0)-1);
left++;
}
res = Math.max(res,right-left);
}
return res;
}
备注
记录,是为了更好的学习,内容如涉及到侵权,请联系我,我会尽快删除。