一道 LeetCode 题,彻底搞懂「滑动窗口」到底在滑什么

4 阅读3分钟

一道 LeetCode 题,彻底搞懂「滑动窗口」到底在滑什么

关键词:滑动窗口 / HashSet / 不变量 / 时间复杂度 O(n)

很多人刷过这道题,但真正理解滑动窗口的人并不多
今天我想用一篇笔记,把这道经典题拆清楚。


一、题目回顾

无重复字符的最长子串

给定一个字符串 s,请你找出其中不含有重复字符的 最长子串 的长度。

示例:

输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc"

二、为什么暴力解法不行?

最直观的思路是:

  • 枚举所有子串
  • 检查是否有重复字符
  • 取最长的

时间复杂度?

  • 子串个数:O(n²)
  • 每个子串检查重复:O(n)

👉 总复杂度:O(n³)
字符串稍微一长,直接超时。


三、核心思想:滑动窗口是什么?

一句话解释:

滑动窗口 = 用两个指针维护一个连续区间,并在区间内维护某种约束条件

在本题中,这个约束条件是:

窗口内的字符必须全部唯一


四、为什么用 Set?

因为我们需要的是:

  • 快速判断某个字符是否已经出现过
  • 快速插入 / 删除

👉 这正是 HashSet 擅长的事情:

Set<Character> occ = new HashSet<>();

你可以把它理解为:

occ 记录的是 当前窗口内已经出现过的字符


五、双指针设计

我们使用两个指针:

  • left:窗口左边界
  • right:窗口右边界

窗口表示的区间是:

[left, right]

六、算法流程

整体思路只有三步:

  1. 右指针不断向右扩展窗口

  2. 如果新字符导致重复:

    • 不断移动左指针
    • 同时从 Set 中移除字符
  3. 每次窗口合法时,更新最大长度


七、完整代码实现

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Set<Character> occ = new HashSet<>();

        int left = 0;
        int ans = 0;

        for (int right = 0; right < s.length(); right++) {
            char c = s.charAt(right);

            // 如果窗口中已经有该字符,不断收缩左边界
            while (occ.contains(c)) {
                occ.remove(s.charAt(left));
                left++;
            }

            // 扩展窗口
            occ.add(c);

            // 更新答案
            ans = Math.max(ans, right - left + 1);
        }

        return ans;
    }
}

八、为什么这里必须用 while,而不是 if?

这是这道题最容易被忽略、但最核心的点

❌ 错误理解

“出现重复了,左指针动一下就好了”

✅ 正确理解

必须一直移动左指针,直到窗口重新满足「无重复字符」这个约束

换句话说:

  • 重复字符可能在窗口中出现多次
  • 一次 if 不一定能修复窗口
  • 必须用 while 持续修复

九、用一个反例说明问题

假设字符串是:

"abba"

当右指针指向第二个 'b' 时:

  • 窗口中已经包含 'b'

  • 如果只用 if

    • 只移除一次左边字符
    • 窗口里仍然有重复的 'b'

👉 窗口状态仍然非法,结果必然出错


十、滑动窗口的“不变量”

整个算法始终维护一个不变量:

在任何一次计算答案时,窗口 [left, right] 内都不存在重复字符

while 的作用正是:

当不变量被破坏时,持续修复,直到不变量重新成立


十一、时间与空间复杂度分析

时间复杂度:O(n)

  • right 指针最多走 n 次
  • left 指针最多走 n 次
  • 每个字符最多被加入和移除一次

👉 虽然有 while,但整体仍是线性复杂度


空间复杂度:O(字符集大小)

  • 最多存储窗口中的字符
  • ASCII 情况下可视为常数级

十二、总结一句话

滑动窗口不是在寻找某一个“完美窗口”,而是在不断维护约束条件下,动态演化出所有合法窗口,并从中取最优解。


十三、写在最后

这道题真正的价值不在于代码本身,而在于它揭示了:

  • 如何用 Set 维护窗口状态
  • 如何用 while 维护算法不变量
  • 为什么滑动窗口能把 O(n²) 优化到 O(n)

理解了这一题,你会发现很多字符串 / 数组问题,本质都在滑同一个窗口。