滑动窗口算法框架

raw/articles/2026/05/滑动窗口框架.md

一句话总结

提供滑动窗口算法的通用代码框架,通过左右指针维护窗口,将子数组/子串问题的时间复杂度从 O(N²) 优化到 O(N)。

核心要点

1. 滑动窗口基本原理

滑动窗口是快慢双指针的应用,维护一个 [left, right) 左闭右开区间作为窗口,通过扩大和缩小窗口寻找满足条件的最优解。

核心思路

  • 第1步:移动 right 扩大窗口,直到满足条件(寻找可行解)
  • 第2步:移动 left 缩小窗口,优化结果(优化可行解)
  • 重复上述过程,直到 right 到达尽头

时间复杂度 O(N) 的原因

  • leftright 指针只增不减,不回退
  • 每个元素只进入窗口一次,离开窗口一次
  • 嵌套 while 循环的总执行次数与 N 成正比(均摊分析)

2. 通用代码框架

// 滑动窗口算法伪码框架
void slidingWindow(String s) {
    // 用合适的数据结构记录窗口中的数据
    // 例如:Map 记录字符出现次数,或 int 记录元素和
    Object window = ...;

    int left = 0, right = 0;
    while (right < s.length()) {
        // c 是将移入窗口的字符
        char c = s[right];
        window.add(c);
        // 增大窗口
        right++;

        // 进行窗口内数据的一系列更新
        ...

        // 判断左侧窗口是否要收缩
        while (left < right && window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            window.remove(d);
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

3. 使用框架的三个关键问题

遇到子串/子数组问题,只需回答:

  1. 何时扩大窗口?窗口加入元素时,应该更新哪些数据?
  2. 何时缩小窗口?移出元素时,应该更新哪些数据?
  3. 何时更新结果?在扩大阶段还是缩小阶段更新?

4. 四道经典题目

LeetCode 力扣 难度 核心考点
76. Minimum Window Substring 76. 最小覆盖子串 Hard 在收缩窗口时更新最短结果
567. Permutation in String 567. 字符串的排列 Medium 定长窗口,窗口大小 = t.length()
438. Find All Anagrams in a String 438. 找到字符串中所有字母异位词 Medium 定长窗口,收集所有合法起始索引
3. Longest Substring Without Repeating Characters 3. 无重复字符的最长子串 Medium 无需 need 和 valid,直接检测重复

时间复杂度

操作 时间复杂度 说明
滑动窗口框架 O(N) 每个元素进出窗口各一次,指针不回退
暴力解法(嵌套for) O(N²) 指针会回退,元素多次进入离开

代码实现

最小覆盖子串(LeetCode 76)

class Solution {
    public String minWindow(String s, String t) {
        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> window = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        int left = 0, right = 0;
        int valid = 0;
        // 记录最小覆盖子串的起始索引及长度
        int start = 0, len = Integer.MAX_VALUE;

        while (right < s.length()) {
            // c 是将移入窗口的字符
            char c = s.charAt(right);
            // 扩大窗口
            right++;
            // 进行窗口内数据的一系列更新
            if (need.containsKey(c)) {
                window.put(c, window.getOrDefault(c, 0) + 1);
                if (window.get(c).equals(need.get(c)))
                    valid++;
            }

            // 判断左侧窗口是否要收缩
            while (valid == need.size()) {
                // 在这里更新最小覆盖子串
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                // d 是将移出窗口的字符
                char d = s.charAt(left);
                // 缩小窗口
                left++;
                // 进行窗口内数据的一系列更新
                if (need.containsKey(d)) {
                    if (window.get(d).equals(need.get(d)))
                        valid--;
                    window.put(d, window.get(d) - 1);
                }
            }
        }
        // 返回最小覆盖子串
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}

最长无重复子串(LeetCode 3)

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> window = new HashMap<>();
        int left = 0, right = 0;
        int res = 0;

        while (right < s.length()) {
            char c = s.charAt(right);
            right++;
            // 进行窗口内数据的一系列更新
            window.put(c, window.getOrDefault(c, 0) + 1);

            // 判断左侧窗口是否要收缩
            while (window.get(c) > 1) {
                char d = s.charAt(left);
                left++;
                // 进行窗口内数据的一系列更新
                window.put(d, window.get(d) - 1);
            }
            // 在这里更新答案(收缩完成后窗口无重复)
            res = Math.max(res, right - left);
        }
        return res;
    }
}

关键技巧

1. 左闭右开区间 [left, right)

  • 初始化 left = right = 0 时窗口为空
  • right++ 后窗口包含 s[0]
  • 避免两端都开或两端都闭带来的边界处理麻烦

2. valid 计数器技巧

  • 用于判断窗口是否已满足所有条件
  • valid == need.size() 时说明窗口已覆盖目标字符集
  • 避免每次都遍历 window 和 need 进行比较

3. Java Integer 比较注意事项

  • 必须使用 equals() 方法而非 == 比较 Integer
  • 错误:window.get(c) == need.get(c)
  • 正确:window.get(c).equals(need.get(c))

应用场景

  • 寻找满足某条件的最长/最短子数组(子串)
  • 字符串包含问题(最小覆盖子串)
  • 排列/异位词检测(定长窗口)
  • 无重复元素子串

相关概念

相关实体

总结

滑动窗口算法通过维护左右指针和窗口数据结构,将嵌套循环的 O(N²) 优化为 O(N)。核心是回答三个问题:何时扩大、何时缩小、何时更新结果。掌握通用框架后,大部分子数组/子串问题都可迎刃而解。


Interactive Graph