leetcode-滑动窗口总结

滑动窗口是我在刷题时感觉比较困难的部分,简单做一个总结,防止之后又忘了:

一般模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 注意: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的排列或者是异位词问题,可以采用固定长度的滑动窗口。一般的套路都是先初始化窗口,然后再滑动判断。

1. 567. 字符串的排列 - 力扣(LeetCode)

直接套模板,比较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;
    }
}
复制代码

 

posted @   Vege_dog  阅读(44)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示