同样是 O(n),为什么你的滑动窗口跑得比别人慢 3 倍?
2026/6/10 0:29:16 网站建设 项目流程

滑动窗口人人会写,但多数人写的其实是 O(2n)。差的那一倍,面试官一眼就能看出来。


📋 题目

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

项目说明
输入s = “abcabcbb”
输出3(最长子串 “abc”)
约束0 ≤ s.length ≤ 5×10⁴,字符集为 ASCII 128

💡 先问一个问题

我跟不少面试官聊过,统一的反馈是:这道题拉出来一问,80% 的人思路都能说对——两个指针框个窗口,没重复就右扩,有了就左缩。

但一到写代码,事就不一样了。超过一半的人会写出一个 O(2n) 的版本,然后自信地在注释里标上 O(n)。

他自己看不出来。但你如果是面试官,你能。

滑动窗口的坑,就藏在 2n 到 n 的这一步。


🤖 第一版:AI 的直觉写法(也是你的直觉)

我问 ChatGPT:「用 Python 写一个求最长无重复子串的函数。」

它给出的第一版代码是这样:

deflengthOfLongestSubstring(s:str)->int:char_set=set()left=0max_len=0forrightinrange(len(s)):whiles[right]inchar_set:char_set.remove(s[left])left+=1char_set.add(s[right])max_len=max(max_len,right-left+1)returnmax_len

看起来很干净对吧?时间复杂度标注 O(n),面试官往往也给过。

问题在哪?

看内层那个while。字符串是"aaaaa"的时候,每处理一个a,left 都得一步步往前挪,每个字符从 set 里 remove 掉一次。一次 add,一次 remove,每个字符被操作了两次。

大 O 上还是 O(n),但常数因子翻倍。


🧠 AI 在对话中是怎么慢慢改对的

光给结论没意思,看对话过程——这才是面试时你应该主动展示的东西。

我问它:「能不能别一步步挪了,直接跳到重复位置?」

deflengthOfLongestSubstring(s:str)->int:char_index={}left=0max_len=0forright,chinenumerate(s):# left 直接跳到重复字符的下一个位置ifchinchar_indexandchar_index[ch]>=left:left=char_index[ch]+1char_index[ch]=right max_len=max(max_len,right-left+1)returnmax_len

就改了三处:

  • setdict,存的是字符的最近位置
  • while整个干掉,换成一次哈希查找
  • left 不再一步一步挪,直接崩到index + 1

这才是货真价实的 O(n),每个字符只摸一次。


暴力枚举
O(n³)

滑动窗口 V1
set + while 收缩
O(2n)

滑动窗口 V2
dict 跳转
O(n)

边界强化
char_index[ch] >= left

char_index[ch] >= left这个判断,是区分「背模板」和「真理解」的试金石。

测一下"abba":处理到第二个a时,哈希表里存着第一个a的位置 0。但 left 已经走到 2 了——这时候还敢跳回 0,窗口直接倒退,答案就错了。

漏掉这个>= left的人,基本就是记住了「用 dict 存位置」,没想过 left 已经越过了旧位置该怎么办。


☕ Java 实现(思路完全一致,给 CSDN 的 Java 开发者)

publicintlengthOfLongestSubstring(Strings){Map<Character,Integer>map=newHashMap<>();intleft=0,maxLen=0;for(intright=0;right<s.length();right++){charc=s.charAt(right);if(map.containsKey(c)&&map.get(c)>=left){left=map.get(c)+1;}map.put(c,right);maxLen=Math.max(maxLen,right-left+1);}returnmaxLen;}

为什么要加 Java?CSDN 第一大用户群体是 Java 开发者。Python 能看懂不代表面试能用 Java 写出来。两版代码摆在一起,读者自己对比,比你讲十句话都有用。


哈希表滑动窗口 [left, right]字符串 "abcabcbb"哈希表滑动窗口 [left, right]字符串 "abcabcbb"right=0, char='a'窗口: [0,0], len=1right=1, char='b'窗口: [0,1], len=2right=2, char='c'窗口: [0,2], len=3 ✓ 最长right=3, char='a' 重复!窗口: [1,3], len=3存储 a→0存储 b→1存储 c→2a 上次在位置 0left 跳转到 1更新 a→3

这张图比文字描述直观得多——每一步 left 是怎么跳的,哈希表怎么更新的,一目了然。


🔍 滑动窗口模式拆解

LeetCode 把滑动窗口归到「连续子串/子数组」的万能套路里。套路是好东西,但背套路的人有一个通病——模板会写,一变形就懵。

滑动窗口的本质就一个东西:维护一个「窗口内无重复」的不变量。

  • 加进来一个字符 → 不变量可能被破坏
  • 不变量被破坏 → 收缩窗口直到恢复

用 set 一步步缩是滑动窗口,用 dict 直接跳也是滑动窗口。模板不重要,你脑子里的那个「窗口内无重复」才重要。

同类题变体一览:

题目窗口收缩条件关键技巧
无重复最长子串出现重复字符dict 存位置直接跳
最小覆盖子串窗口内包含所有目标字符双哈希表计数
长度最小的子数组窗口和 ≥ target累加和 + while 收缩

🏗️ 真实产品场景

我知道有人觉得这种题面试造火箭,工作拧螺丝。说几个真实用到的场景:

1. 日志去重窗口

监控系统的日志流水线,每秒可能吐出上百条重复的错误日志。产品需求是「在最近 N 秒内,同一条错误日志只触发一次告警」。

你把错误消息 hash 一下当字符串,维护一个时间窗口,不重复的消息才放行——内核逻辑跟今天这题一模一样。区别只是窗口收缩条件从「出现重复字符」变成了「时间戳过期」,但代码骨架完全复用。

2. Netflix 播放断点检测

流媒体服务的播放数据里,同一个 session 的 seek 事件可能连续多次跳到同一秒(用户疯狂拖进度条)。后端要找出「没有重复跳转位置的最长连续播放段」来判定有效观看时长。

字符串换成播放位置的时间数组,滑动窗口往里一套,就是这道题。

3. 消息队列去重

Kafka 消费者本地缓存最近 1000 条消息 ID,用滑动窗口确保同一个 ID 在窗口内只处理一次。窗口滑出旧消息、滑入新消息,窗口内无重复 ID——这个约束和今天的题完全同构。

顺便说一句,Redpanda 和 Apache Pulsar 的 dedup 实现里,底层用的就是类似的滑动窗口逻辑。不是玩具,是真跑在生产环境里的。


✅ 面试官的点评

我模拟过这道题的面试,以下是一个有经验的面试官真实的打分心理:

写到什么程度能过:

  • set + while 版本,能说清楚为什么不退化成 O(n²) → 及格。注意,面试官在这个阶段真正想听的是「每个字符被 add 一次、remove 一次,加起来 O(2n) = O(n)」,听到这句话他就放心了。
  • 主动提到 dict 可以优化掉 while → 良好。这时候面试官会追问「那为什么第一版不直接用 dict」,你如果能答出「set 版本更直观,先保证正确性再优化」,直接加分。
  • char_index[ch] >= left写对了,拿"abba"测一遍不出错 → 优秀。到这个程度,面试官已经在想 offer 的事了。
  • 顺嘴提一句「ASCII 128 可以直接用int[128]代替哈希表,空间 O(1)」→ 满分。这属于降维打击——大部分候选人根本想不到。

翻车姿势一览:

  • "abba"测试用例直接挂——十有八九是漏了>= left。面试官看到这,大概率已经在心里给你打了个问号。
  • set 版本里 while 半天不动——remove完忘了left += 1,死循环。这种低级错误发生一次,后面的表现再好都很难拉回来。
  • 空字符串直接炸——s[0]都不看一眼。没有边界检查的行为,是面试官最忌讳的。

📊 同类题推荐

  • LeetCode 76 - 最小覆盖子串(Hard):滑动窗口 + 双哈希表,窗口收缩条件更复杂
  • LeetCode 209 - 长度最小的子数组(Medium):纯数字版滑动窗口,入门必做
  • LeetCode 159 - 至多包含两个不同字符的最长子串(Medium):今天这题的「允许 K 个不同字符」扩展版

来源说明

  • ✅ 已验证:LeetCode 官方题解 + ChatGPT/Claude 多轮实测
  • 📄 参考:“Sliding Window Technique” — 算法导论模式归纳

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询