算法学习-滑动窗口

1,467 阅读2分钟

滑动窗口技巧

滑动窗口算法的技巧:

滑动窗口算法的思路:

  1. 我们在字符串 S中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right)称为一个「窗口」。
  2. 不断地增加 right指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T  中的所有字符)。
  3. 此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
  4. 重复第 2 和第 3 步,直到 right到达字符串 S的尽头。

重点:

第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。

模板,只需要思考以下几个问题

1、什么时候应该移动 right 扩大窗口?窗口加入字符时,应该更新哪些数据?

2、什么时候窗口应该暂停扩大,开始移动 left 缩小窗口?从窗口移出字符时,应该更新哪些数据?

3、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?

如果一个字符进入窗口,应该增加 window 计数器;如果一个字符将移出窗口的时候,应该减少 window 计数器;当 valid 满足 need 时应该收缩窗口;应该在收缩窗口的时候更新最终结果

适用场景:

子数组/子串相关的问题

主要要记得:

1、什么时候应该扩大窗口?

2、什么时候应该缩小窗口?

3、什么时候应该更新答案?

76. 最小覆盖子串

image.png

题解:

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. 字符串的排列

image.png

题解:

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. 找到字符串中所有字母异位词

image.png

题解:

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. 无重复字符的最长子串

image.png

题解:

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; 
}

备注

记录,是为了更好的学习,内容如涉及到侵权,请联系我,我会尽快删除。

参考地址,并推荐给大家:

labuladong 的算法小抄