当前位置: 首页 > news >正文

最长无重复子串 — 从暴力到滑动窗口,AI 是怎么一步步优化的?

读完本文你将了解:最长无重复子串的 3 种解法演进 | 滑动窗口模式的核心原理 | 面试中的常见变体


📋 题目

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

项目说明
输入s = “abcabcbb”
输出3
解释最长无重复子串是 “abc”,长度为 3
约束0 ≤ s.length ≤ 5×10⁴,s 由英文字母、数字、符号和空格组成

💡 先问一个问题

第一次给 AI 看这道题,它会怎么写?

大多数人第一反应是遍历所有子串,检查每个子串是否包含重复字符。AI 也一样。不是因为它笨,人的直觉就是这样思考的。

🤖 第一版:AI 的朴素解法(暴力枚举)

deflength_of_longest_substring(s:str)->int:"""暴力枚举所有子串,O(n³)"""n=len(s)max_len=0foriinrange(n):forjinrange(i,n):iflen(set(s[i:j+1]))==j-i+1:max_len=max(max_len,j-i+1)returnmax_len

这个代码能跑,但面试官看了会直接叫停。三层嵌套:外层循环起点 i,内层循环终点 j,set()内部又是一层隐式循环。总复杂度 O(n³),n=50000 时完全不可用。

复杂度分析:时间 O(n³) 空间 O(min(n, ∣Σ∣))

🧠 AI 的自我优化

第 1 次优化:滑动窗口 O(n²)

AI 很快意识到不需要每次都重建set。改用两个指针维护一个窗口,窗口内保证无重复字符:

deflength_of_longest_substring(s:str)->int:"""滑动窗口 + set,O(2n)"""n=len(s)char_set=set()left=0max_len=0forrightinrange(n):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³) 降到 O(2n) ≈ O(n)。不再每次从零检查子串,而是让 left 指针追赶 right 指针,保证窗口内始终无重复。但这里有个问题——while 循环中每次只移除 left 指向的字符,如果重复字符在窗口中间而不是左端,需要多次循环才能移除。

暴力枚举
O(n³)

滑动窗口+set
O(2n)

优化版:HashMap
直接跳转 left
O(n)

第 2 次优化:HashMap 直接跳转 O(n)

真正的最优解是用 HashMap 记录每个字符最近出现的位置,遇到重复时直接跳转 left:

deflength_of_longest_substring(s:str)->int:"""HashMap 记录字符位置,O(n) 一次遍历"""char_index={}left=0max_len=0forright,chinenumerate(s):ifchinchar_indexandchar_index[ch]>=left:left=char_index[ch]+1char_index[ch]=right max_len=max(max_len,right-left+1)returnmax_len

从 O(2n) 降到精确 O(n)——left 指针不再一步步挪,而是一次跳到位。这是滑动窗口模式中最核心的技巧:不要用 while 循环逐步收缩窗口,用 HashMap 直接定位跳转点。

☕ Java 实现(思路完全一致)

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

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

🔍 算法模式拆解

这道题是典型的滑动窗口(Sliding Window)模式。

初始化 left=0, max_len=0

扩展 right, 记录字符位置

字符重复且在窗口内?

left 跳到重复位置+1

更新 max_len

right 遍历完?

返回 max_len

模式识别

滑动窗口解决的是连续子序列/子数组问题,核心特征:

特征本题表现
问题要求"连续"子串天然连续
窗口边界可变left/right 动态调整
窗口内需满足约束无重复字符
求最大/最小/最优值求最大长度

通用模板

defsliding_window(s):"""滑动窗口通用模板"""left=0result=0window={}forright,chinenumerate(s):window[ch]=window.get(ch,0)+1while不满足约束条件:remove window[s[left]]left+=1result=max(result,right-left+1)returnresult

模式变体

  • 最多包含 K 个不同字符的最长子串(LeetCode 340):约束从"无重复"变为"最多 K 种字符"
  • 最小覆盖子串(LeetCode 76):约束变为"包含目标字符串的所有字符"
  • 无重复字符的最长子数组:字符换成数字,逻辑完全一致

🏗️ 真实产品场景

这个模式在实际产品中无处不在:

Slack 的会话超时检测:每个用户有一个活跃会话 Token,服务器需要检测一个 Token 序列中最长的不重复 Token 段——和本题一模一样。如果检测到重复 Token 出现在同一个会话窗口内,说明 Token 被重放攻击了。

Netflix 的推荐多样性:当为用户推荐视频列表时,需要保证连续推荐中不出现重复的类别或演员。这本质上就是在维护一个"无重复滑动窗口"。

Google 搜索的连续查询去重:用户在短时间内反复搜索相同的关键词,搜索结果页需要识别并折叠这些重复查询,只保留最新的一次。

✅ 面试官的点评

通过标准(保底分)

  • 能写出滑动窗口 + set 的 O(2n) 版本
  • 能说清楚为什么暴力解不可用
  • 能分析时间复杂度

加分项(冲满分)

  • 写出 HashMap 跳转版 O(n):这才是面试官想看到的
  • 能处理字符集不限于 ASCII:问一句"输入字符集有多大?"——如果支持 Unicode 全部字符,HashMap 比固定数组更合适
  • 边界条件处理:空字符串返回 0,单字符返回 1
  • 主动聊产品场景:说出上面的 Slack/Netflix 场景,面试官会点头

常见踩坑

  • 用 int[128] 代替 HashMap(仅限 ASCII,不是通用解)
  • while 循环中 left++ 而非直接跳转(多耗常数时间,但面试中可接受)
  • 忘记检查char_index[ch] >= left(HashMap 记录了所有历史位置,不在当前窗口内的旧位置不应触发跳转)

📊 同类题推荐

题目难度一句话思路
LeetCode 340: 最多 K 个不同字符的最长子串Medium把约束从"无重复"改成"最多 K 种",用 Counter 计数
LeetCode 76: 最小覆盖子串Hard用 Counter + need/have 匹配,收缩窗口找最小
LeetCode 424: 替换后的最长重复字符Medium窗口内维护最多字符的频次,用总长 - 最多频次 ≤ K 判断

来源说明

  • ✅ 已验证:LeetCode 3 官方题解 + AI 实测
  • 📄 文档/论文:《算法导论》滑动窗口章节
http://www.jsqmd.com/news/1174566/

相关文章:

  • MinGW-w64 编译 OpenSSL 3.0.0:Windows 10/11 双平台静态库生成,大小优化 30%
  • 5G 网络仿真调测:3 类常见告警(版本/邻区/连接)的定位与修复
  • Unity协程实战:从原理到对象池优化,实现高效敌人生成系统
  • Erjang高级配置指南:OTPROOT设置与运行时参数优化的10个实用技巧
  • 2026年7月最新长春浪琴官方售后服务热线与网点地址查询 - 浪琴官方售后服务中心
  • 震撼发布:quake_watch——将经典《雷神之锤1》完美移植到Apple Watch的终极游戏体验
  • Unity UI Toolkit实战:从视觉树到数据驱动的高性能UI开发
  • Unity Shader Graph实现2D不规则艺术描边:从原理到动态效果实战
  • GTA5线上小助手:开源免费的游戏体验增强工具完全指南
  • OpenTelemetry-proto开发者指南:10个最佳实践与常见陷阱
  • 个人可以做GEO系统OEM贴牌吗
  • 2026 天津回收黄金店在哪里,优先推荐易奢福 - 肉松卷
  • 5步教程:使用NVIDIA Llama-Nemotron-Colembed-VL-3B-V2构建企业级RAG系统
  • 毕设开源 yolov8叶片病害检测系统(源码+论文)
  • PyCharm 2024.3 社区版配置:5个必改设置提升Windows开发效率
  • Win11Debloat:Windows 11终极性能优化与隐私保护完全指南
  • 如何用Puppet PadLocal实现自动回复机器人
  • 2026年7月最新唐山欧米茄官方售后客户服务热线与维修网点地址汇总 - 欧米茄官方服务中心
  • 深度解析大气层架构:从内核到应用层的完整技术实现方案
  • 快速配置语音实时翻译:跨语言对话完整指南
  • 【预定SCI2区】基于麻雀搜索优化算法SSA-BiTCN-BiGRU-Attention的风电预测算法研究Matlab实现
  • Ollama num_gpu参数与性能关系测试
  • 未来展望:Qwen3.5-122B-A10B-OptiQ-2bit在边缘计算和本地AI的应用前景
  • 5分钟掌握Godot碰撞层与遮罩:从原理到实战实现子弹穿墙
  • UnityMMO开源框架:快速构建3D多人在线游戏的完整指南
  • 大唐杯 5G 仿真:从 BBU 板卡到核心网 7 大模块 IP 地址规划实战
  • 鑫宸黄金奢品回收携六店联动!榆林全域黄金回收,到店上门都OK - 清奢黄金上门回收
  • Stream-Translator 实时流媒体翻译终极指南
  • CANN/asc-devkit:SIMD与SIMT混合编程HashTable MTE队列优化样例
  • 终极指南:5分钟掌握Silero VAD语音活动检测技术