leetcode-滑动窗口总结
滑动窗口是我在刷题时感觉比较困难的部分,简单做一个总结,防止之后又忘了:
一般模板如下:
// 注意:java 代码由 chatGPT🤖 根据我的 cpp 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
// 本代码还未经过力扣测试,仅供参考,如有疑惑,可以参照我写的 cpp 代码对比查看。
/* 滑动窗口算法框架 */
void slidingWindow(String s) {
    // 用合适的数据结构记录窗口中的数据
    Map<Character, Integer> window = new HashMap<Character, Integer>();
    
    int left = 0, right = 0;
    while (right < s.length()) {
        // c 是将移入窗口的字符
        char c = s.charAt(right);
        window.put(c, window.getOrDefault(c, 0) + 1);
        // 增大窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...
        /*** debug 输出的位置 ***/
        // 注意在最终的解法代码中不要 print
        // 因为 IO 操作很耗时,可能导致超时
        System.out.printf("window: [%d, %d)\n", left, right);
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (left < right && window needs shrink) {
            // d 是将移出窗口的字符
            char d = s.charAt(left);
            window.put(d, window.get(d) - 1);
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}
但其实上面的模板看看就得了,其实这类题目写法可以参考下面的题目,通过对比几道题目就能发现滑动窗口题目的共通之处。(重点在于需要创建一个表示window的数据结构--通常可以使用HashMap表示元素与次数的对应,特殊情况如题目明确表示为26个小写字母时,可以采用固定长度的数组表示以提高效率--以及窗口左右两个指针。)
重点需要思考以下三个问题:
1、什么时候应该移动 right 扩大窗口?窗口加入字符时,应该更新哪些数据?
2、什么时候窗口应该暂停扩大,开始移动 left 缩小窗口?从窗口移出字符时,应该更新哪些数据?
3、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
滑动窗口的题目不能简单理解为扩大窗口+缩小窗口,其实是分几类情况的,我一一阐述:
I. 不定长滑动窗口类:
1. 求最小子串问题:
这类题目通常需要采用扩大窗口+缩小窗口的范式,典型的题目有力扣题有76. 最小覆盖子串 - 力扣(LeetCode):
这道题显然可以直接套用模板,在设置window的情况下,用一个哈希表(need)来记录需求(这里有一个小trick是采用了一个count来记录当前窗口内的子串是否符合条件,确认需求的复杂度可以变成O(1)),代码如下:
import java.util.HashMap; import java.util.Map; class Solution { public String minWindow(String s, String t) { //res用来记录最小子串的长度 int res = Integer.MAX_VALUE; Map<Character, Integer> need = new HashMap<>(); int count = 0; int min_start = 0; //初始化need for(char c : t.toCharArray()){ need.put(c, need.getOrDefault(c, 0) + 1); count++; } int i = 0; for(int j = 0; j < s.length(); j++){ if(need.containsKey(s.charAt(j))){ if(need.get(s.charAt(j)) > 0) count--; need.put(s.charAt(j), need.get(s.charAt(j)) - 1); } while(count == 0){ if(need.containsKey(s.charAt(i))){ need.put(s.charAt(i), need.get(s.charAt(i)) + 1); if(need.get(s.charAt(i)) > 0) count++; if(j - i + 1 < res) { res = j - i + 1; min_start = i; } } i++; } } return res == Integer.MAX_VALUE ? "" : s.substring(min_start, min_start + res); } }
II. 定长滑动窗口类
对于判断字符串s1中是否包含字符串s2的排列或者是异位词问题,可以采用固定长度的滑动窗口。一般的套路都是先初始化窗口,然后再滑动判断。
直接套模板,比较tricky的还是如何在满足条件判断上,这里通过统计s1中不同的字母个数以及设置一个valid变量记录当前窗口满足条件的字母的类别总数以判断是否满足条件。
class Solution { //套模板 public boolean checkInclusion(String s1, String s2) { char[] need = new char[26]; char[] window = new char[26]; int size = 0, valid = 0; //初始化need for(char c : s1.toCharArray()){ if(need[c - 'a'] == 0) size++; need[c - 'a'] += 1; } int left = 0, right = 0; //左闭右开 //先初始化固定窗口,再移动窗口 while(right < s2.length()){ //右指针右移动 char temp1 = s2.charAt(right); if(need[temp1 - 'a'] > 0){ window[temp1 - 'a'] += 1; if(window[temp1 - 'a'] == need[temp1 - 'a']) valid++; } right++; //当满足right - left == s1.length()时,窗口固定 if(right - left == s1.length()){ //左指针右移 if(valid == size) return true; char temp2 = s2.charAt(left); if(need[temp2 - 'a'] > 0){ if(window[temp2 - 'a'] == need[temp2 - 'a']) valid--; window[temp2 - 'a'] -= 1; } left++; } } return false; } }
2. 438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
直接上代码,和刚才基本一样:
class Solution { //之前解法太复杂,本质上就是个固定滑动窗口问题 //直接上模板!!! public List<Integer> findAnagrams(String s, String p) { List<Integer> res = new LinkedList<>(); char[] need = new char[26]; char[] window = new char[26]; int size = 0, valid = 0; //初始化need for(char c : p.toCharArray()){ if(need[c - 'a'] == 0) size++; need[c - 'a'] += 1; } int left = 0, right = 0; while(right < s.length()){ char temp1 = s.charAt(right); if(need[temp1 - 'a'] > 0){ window[temp1 - 'a'] += 1; if(window[temp1 - 'a'] == need[temp1 - 'a']) valid++; } right++; if(right - left == p.length()){ if(valid == size) res.add(left); char temp2 = s.charAt(left); if(need[temp2 - 'a'] > 0){ if(window[temp2 - 'a'] == need[temp2 - 'a']) valid--; window[temp2 - 'a'] -= 1; } left++; } } return res; } }
III. 最长子串问题
1. 3. 无重复字符的最长子串 - 力扣(LeetCode)
本质上也不定长度的滑动窗口问题,但是因为是求最长子串而不是最小子串,所以更新答案的位置在外循环结束前。
class Solution { public int lengthOfLongestSubstring(String s) { //不再需要need Map<Character, Integer> window = new HashMap<>(); int right = 0, left = 0; boolean valid = true; int res = 0; while(right < s.length()){ char temp1 = s.charAt(right); window.put(temp1, window.getOrDefault(temp1, 0) + 1); right++; //发现不满足条件了就得左边右移 while(window.get(temp1) > 1){ char temp2 = s.charAt(left); window.put(temp2, window.get(temp2) - 1); left++; } //在这里更新答案。。。。。。 res = Math.max(res, right - left); } return res; } }
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号