题目
给定字符串 s,求最长的不含重复字符的连续子串长度。
示例:
- 输入:
s = "abcabcbb" - 输出:
3(最长子串为"abc")
解题思路
使用滑动窗口(双指针)维护一个无重复字符的窗口 [j..i],其中:
i是右指针,不断向右扩张窗口j是左指针,标记当前无重复子串的起始位置- 用 HashMap 记录每个字符最近一次出现位置的下一位
核心不变量:
- 窗口内没有重复字符
- 左指针
j只会右移,不会左移
当遇到重复字符时,左指针需要跳到上次出现位置的下一位,但必须使用 max(j, map[ch]) 确保 j 不会倒退。
为什么必须用 max?
以 s = "abba" 为例:
index: 0 1 2 3
s: a b b a
当 i=3 遇到第二个 ‘a’ 时:
- 如果不用 max:
j = map['a'] = 1,窗口变成[1..3] = "bba",包含重复的 ‘b’ - 使用 max:
j = max(2, 1) = 2,窗口保持[2..3] = "ba",正确
代码实现
import java.util.HashMap;
class Solution {
public int lengthOfLongestSubstring(String s) {
// map: 存储每个字符最近一次出现位置的"下一位"
HashMap<Character, Integer> map = new HashMap<>();
// res: 记录目前找到的最长无重复子串长度
int res = 0;
// i: 右指针,遍历字符串
// j: 左指针,标记当前无重复子串的起始位置
for (int i = 0, j = 0; i < s.length(); i++) {
char ch = s.charAt(i);
// 如果当前字符已经在窗口中出现过
// 更新左指针 j 到"上次出现位置的下一位"
// 使用 max 确保 j 只会右移,不会倒退
if (map.containsKey(ch)) {
j = Math.max(j, map.get(ch));
}
// 更新最长长度:当前窗口大小是 i-j+1
res = Math.max(res, i - j + 1);
// 记录当前字符的"下一位",为下次遇到该字符做准备
map.put(ch, i + 1);
}
return res;
}
}
代码解释
- 初始化:创建 HashMap 存储字符位置,res 记录最大长度,双指针 i 和 j 初始为 0
- 右指针扩张:i 遍历整个字符串,每次取当前字符
- 处理重复:如果字符已存在,通过
max(j, map[ch])更新左指针 - 更新答案:每次计算当前窗口长度
i - j + 1,更新最大值 - 记录位置:将当前字符的下一位存入 map
速记
滑动窗口 + HashMap:右指针扩张,遇重复则左指针跳转,使用 max 防止左指针倒退。
注意点
- 左指针必须用 max:防止 j 倒退导致窗口包含重复字符
- map 存 i+1:存储下一位置而非当前位置,更新 j 时更方便
- 窗口长度计算:
i - j + 1,不要写错 - 空字符串处理:循环条件
i < s.length()已处理,无需额外判断
复杂度分析
- 时间复杂度:O(n),每个字符最多进出窗口一次
- 空间复杂度:O(min(n, m)),m 为字符集大小