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

从暴力到优雅:LeetCode 3. 无重复字符的最长子串 深度解析

📌 前言:为什么它是面试之王?

在 LeetCode 的“Hot 100”榜单中,第 3 题《无重复字符的最长子串》​ 绝对是面试界的“钉子户”。

这道题看似简单,实则考察了:

  1. 双指针思想(滑动窗口的本质)

  2. 哈希表的高效查询

  3. 边界条件的精准控制

很多同学卡在“为什么左指针要那么跳?”、“Map 里到底存什么?”。今天,我将用最通俗的语言,带你彻底拆解这个经典难题。


🟢 题目速览

LeetCode 3. 无重复字符的最长子串

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

输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

🧠 思路进化:从 O(n³) 到 O(n)

方法一:暴力枚举(理解题意用)

核心思想:

枚举所有可能的子串(起点 i,终点 j),然后检查这个子串里是否有重复字符。

缺点:

  • 枚举子串 O(n²),检查重复 O(n),总复杂度高达O(n³)

  • 绝对会在大数据面前超时。

结论:​ 仅用于理解题意,实战不可用。


方法二:滑动窗口 + 哈希表(标准答案)

这是本题的正解

1. 什么是“滑动窗口”?

想象你的手指在字符串上方画了一个框(窗口),这个框有左右两个边界:

  • 右边界 (right):负责探索新字符,不断向右扩大窗口。

  • 左边界 (left):当窗口内出现重复字符时,负责向右收缩窗口,直到把重复的那个字符“挤”出去。

2. 哈希表的作用

我们维护一个Map<Character, Integer>,用来记录:

  • Key:窗口内的字符

  • Value:该字符最近一次出现的位置

3. 核心逻辑拆解

我们以s = "abba"为例,看看窗口是如何变化的:

步骤

right

字符

Map 状态

left

操作逻辑

1

0

a

{a=0}

0

无重复,更新最大长度

2

1

b

{a=0, b=1}

0

无重复,更新最大长度

3

2

b

{a=0, b=2}

2

发现重复!map.get(b)=1 >= left,所以left = 1 + 1 = 2

4

3

a

{a=3, b=2}

3

发现重复!map.get(a)=0 < left,无需移动 left

注意第 4 步:这就是为什么代码中要用left = Math.max(left, ...),防止左指针倒退!

4. 代码实现(Java 最优解)
class Solution { public int lengthOfLongestSubstring(String s) { // 边界处理 if (s == null || s.length() == 0) { return 0; } // key: 字符, value: 字符最近一次出现的索引 Map<Character, Integer> map = new HashMap<>(); int left = 0; // 窗口左边界 int maxLen = 0; // 最长子串长度 for (int right = 0; right < s.length(); right++) { char c = 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); } return maxLen; } }

🚀 进阶优化:数组代替 HashMap(面试加分项)

如果题目中明确说明字符串只包含ASCII 字符(或者字符集范围已知),我们可以用int[]数组来代替HashMap,进一步提高效率。

原理:

字符本质上就是数字(ASCII 码),我们可以直接用字符作为数组下标。

class Solution { public int lengthOfLongestSubstring(String s) { int[] index = new int[128]; // ASCII 字符集 int left = 0; int maxLen = 0; for (int right = 0; right < s.length(); right++) { char c = s.charAt(right); // 直接取上次出现的位置,与 left 比较 left = Math.max(left, index[c]); // 更新字符最近出现的位置 (+1 是为了方便计算) index[c] = right + 1; maxLen = Math.max(maxLen, right - left + 1); } return maxLen; } }

📊 复杂度分析

解法

时间复杂度

空间复杂度

评价

暴力法

O(n³)

O(1)

❌ 不可用

滑动窗口 (HashMap)

O(n)

O(min(m, n))

✅ 标准解

滑动窗口 (数组)

O(n)

O(1)

✅✅ 面试最优解

(注:m 为字符集大小)


💡 总结与套路

滑动窗口的通用模板:

初始化左指针 left = 0 初始化数据结构(Map/Set/数组) for 右指针 right 从 0 到 n-1: 处理 right 指向的元素 while 窗口不满足条件(如出现重复): 移除 left 指向的元素 left++ 更新结果

一句话心得:

滑动窗口的本质是双指针的快慢游戏

右指针负责“探路”,左指针负责“止损”。

哈希表则是你的“侦察兵”,负责告诉你哪里出现了重复。

掌握了这道题,你就掌握了解决“子串 + 最值”​ 类问题的万能钥匙。

http://www.jsqmd.com/news/794869/

相关文章:

  • 微信网页版终极解决方案:三步实现浏览器端微信完整使用指南
  • 2026重庆雅思培训口碑实测:本土与连锁机构的横向对比 本地特色机构 深耕本地 更适合重庆考生 - 奔跑123
  • 芯片验证中软件仿真与硬件仿真的协同策略与实战指南
  • 如何让老旧安卓电视焕发新生:MyTV-Android开源直播应用终极指南
  • 2026年成都股权搭建咨询服务费大揭秘!TOP7权威排行榜推荐必看 - 品牌推荐官方
  • 终极Mac窗口置顶指南:如何用Topit实现200%工作效率提升
  • 基于MCP协议的AI智能体自动化评估工具agentscore-mcp详解
  • 如何让Windows任务栏变透明:TranslucentTB终极美化指南
  • GitHub中文化插件:3分钟让GitHub界面全中文,开发效率提升50%![特殊字符]
  • 为妈妈送上专属关怀 京东母亲节推出0.01元领燕窝、珍珠耳饰、鲜花等关怀礼活动 - 博客万
  • 网易游戏NPK文件解包终极指南:3步快速提取阴阳师等游戏资源
  • 【Matlab】视频行为识别与异常检测算法设计与仿真实现
  • swiftLLM:大模型推理加速实战,从算子融合到量化部署
  • 突破大语言模型平滑化诅咒:Emergence Codex语义架构语言实战指南
  • Android Studio中文界面5分钟搞定:告别英文困扰的开发新体验
  • 告别标准库!用STM32CubeMX和HAL库驱动ILI9341 SPI屏(附完整代码与取模工具)
  • 镜像贯通虚实 孪生赋能千行 :高适配环境鲁棒能力,适配多行业复杂场景数字孪生深度应用
  • 【Tools】MarkDown进阶:用表格与公式构建专业技术文档
  • 2026年成都AI搜索优化公司TOP6深度评测报告,贴心之选大揭秘! - 品牌推荐官方
  • 2026年LED面罩美容仪美容面罩怎么选?这份选购测评推荐请收好! - 博客万
  • Visual Studio 2019实战:从源码编译到项目集成Libcurl全解析
  • Axolotl中的SFT、DPO与RLHF流程解析-原理源码解析
  • 别再让CPU当‘搬运工’了!5分钟搞懂DMA如何帮你解放CPU,提升程序性能
  • 从零到一:ORB-SLAM2实战EuRoC数据集与EVO精度评测全记录
  • StreamCap:一站式多平台直播录制解决方案,轻松捕获40+平台精彩内容
  • 哪家仿真训练资源管理系统的性价比高? - myqiye
  • 丹佛斯动态平衡阀采购全攻略:ASV-PV与VFG2-AFP靠谱供应商盘点 - 品牌推荐大师
  • 无标实时动态重构 全域智慧孪生:毫秒级空间解算能力,支撑视频孪生态势推演与主动预警
  • 原神60帧限制突破指南:解锁高帧率游戏体验的完整解决方案
  • 2026年成都制作产品宣传片视频TOP7权威排行榜,为你揭晓! - 品牌推荐官方