LeetCode 3. 无重复字符的最长子串 ✅ | Java 双指针 + 哈希表解法详解(小白秒懂版)
·
Hi 各位小伙伴👋,今天咱们来搞定 LeetCode 第 3 题 ——无重复字符的最长子串,这道题是面试高频的「滑动窗口」入门题,我会用最通俗的话,结合我的代码一步步讲透,看完你也能轻松写出!
先看懂题目:到底要找啥?
题目给我们一个字符串 s,要找出最长的、里面没有重复字符的连续子串,返回它的长度。
举个例子:
- 输入:
s = "abcabcbb"→ 最长无重复子串是"abc",长度为 3 - 输入:
s = "bbbbb"→ 最长无重复子串是"b",长度为 1 - 输入:
s = "pwwkew"→ 最长无重复子串是"wke",长度为 3
注意:子串必须是连续的,像 "pwke" 这种跳着选的是「子序列」,不算数!
我的思路:用「滑动窗口」找最长无重复串
我最开始想:暴力枚举所有子串,挨个检查有没有重复字符,这肯定能做,但时间复杂度是 O (n²),字符串长一点就超时了。
后来想到用滑动窗口(双指针)+ 哈希表的思路:
- 用两个指针
l(左)和r(右),框出一个「窗口」,窗口里的字符都是无重复的 - 用哈希表记录窗口里每个字符的出现次数,方便快速查重
- 右指针
r不断往右扩,把新字符加进窗口;如果发现重复字符,就把左指针l往右挪,直到窗口里没有重复字符 - 每次调整完窗口,都更新一下「最长长度」
代码逐行拆解
完整代码
class Solution {
public int lengthOfLongestSubstring(String s) {
// 滑动窗口 + 哈希表查重
// 哈希表:key=字符,value=当前窗口内该字符出现的次数
HashMap<Character, Integer> map = new HashMap<>();
// 左右指针:l=窗口左边界,r=窗口右边界
int l = 0, r = 0;
// 记录最长无重复子串的长度
int maxLong = 0;
// 右指针一直往右走,直到遍历完整个字符串
while (r < s.length()) {
// 1. 如果当前字符(s.charAt(r))在窗口里已经出现过(次数≥1)
// 就把左指针往右挪,直到窗口里没有重复字符
while (map.getOrDefault(s.charAt(r), 0) >= 1) {
// 把左指针指向的字符从窗口里“移除”:次数减1
map.put(s.charAt(l), map.getOrDefault(s.charAt(l), 0) - 1);
// 左指针右移
l++;
}
// 2. 现在窗口里没有重复字符了,把当前字符加进窗口:次数+1
map.put(s.charAt(r), map.getOrDefault(s.charAt(r), 0) + 1);
// 3. 右指针继续往右扩
r++;
// 4. 更新最长长度:当前窗口长度 = r - l
maxLong = Math.max(maxLong, r - l);
}
// 遍历结束,返回最长长度
return maxLong;
}
}
关键步骤拆解(跟着我走一遍就懂)
我们拿示例 s = "abcabcbb" 来演示:
1. 初始化
l=0,r=0,maxLong=0,哈希表为空- 右指针
r从 0 开始,逐个字符往后走
2. 右指针扩窗口(无重复时)
r=0(字符a):哈希表没a,直接加进去 →map={a:1},r=1,maxLong=1r=1(字符b):哈希表没b,加进去 →map={a:1,b:1},r=2,maxLong=2r=2(字符c):哈希表没c,加进去 →map={a:1,b:1,c:1},r=3,maxLong=3
3. 遇到重复字符,左指针缩窗口
r=3(字符a):哈希表a次数是 1(≥1),进入内层循环:- 把
l=0指向的a次数减 1 →map={a:0,b:1,c:1},l=1 - 现在
a次数是 0,退出内层循环
- 把
- 把
a加进窗口 →map={a:1,b:1,c:1},r=4,maxLong还是 3(窗口长度还是 3)
4. 继续遍历,直到结束
后面 r 继续走到 4(b)、5(c)…… 都会触发类似的「缩窗口」操作,但最长长度始终保持 3,最终返回 3。
核心细节讲透
1. 为什么用 getOrDefault?
map.getOrDefault(s.charAt(r), 0) 是为了避免 null 异常:
- 如果字符不在哈希表里,默认返回 0,代表没出现过
- 如果在表里,就返回它的出现次数,方便判断是否重复
2. 为什么窗口长度是 r-l?
- 右指针
r是已经加进窗口的下一个位置,所以当前窗口里的字符是[l, r-1] - 长度 =
(r-1) - l + 1 = r - l,直接算r-l就对了
3. 内层循环的作用:「去重」
当右指针遇到重复字符时,我们必须把左指针往右挪,直到重复字符被移出窗口,这样才能保证窗口里永远是「无重复字符」的子串。
复杂度分析
- 时间复杂度:O (n)。每个字符最多被左指针和右指针各遍历一次,所以整体是线性时间。
- 空间复杂度:O (min (m, n))。哈希表最多存
m个不同字符(比如字符集是 26 个字母),n是字符串长度。
我踩过的坑(新手避坑指南)
- 忘记更新
maxLong:只记得扩窗口,忘了每次扩完都要算一下当前窗口长度,导致最后返回 0。 - 内层循环条件写错:写成
map.containsKey(s.charAt(r)),会导致重复字符已经被移出窗口后还在循环,要判断「出现次数≥1」。 - 窗口长度算错:写成
r-l+1,会多算一个字符,因为r已经指向下一个位置了。 - 字符串为空:如果
s是空串,直接返回 0,代码里while(r<s.length())会直接不执行,返回maxLong=0,刚好处理这种情况。
小结:滑动窗口解题三步口诀
- 右指针扩窗口:把新字符加进哈希表,记录出现次数
- 遇重复就缩窗口:左指针往右挪,直到窗口里无重复
- 更新最长长度:每次调整完窗口,都算一下当前长度,更新最大值
更多推荐

所有评论(0)