力扣解题-无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
示例 1: 输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。
示例 2: 输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3: 输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示: 0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
第一次解答
解题思路
核心方法:滑动窗口(双指针) + 哈希集合去重,通过动态调整窗口的左右边界,在一次遍历中找到无重复字符的最长子串,将暴力解法的O(n²)时间复杂度优化至O(n),是该问题的经典最优解法。
具体步骤:
- 核心原理铺垫 :滑动窗口是解决"子串/子数组"类问题的常用技巧,本题中用
left和right两个指针分别表示窗口的左、右边界,窗口[left, right]内始终维护一个"无重复字符的子串",通过动态调整边界来找到最长窗口长度。 - 初始化关键变量 :
seen:HashSet<Character>集合,用于快速判断当前字符是否已在窗口内(O(1)查找效率),存储当前窗口内的所有字符;left:窗口左指针,初始值为0,代表窗口起始位置;maxLength:记录遍历过程中找到的最长无重复子串长度,初始值为0。
- 右指针遍历扩展窗口 :
- 右指针
right从0开始遍历字符串的每个字符c = s.charAt(right),代表尝试将当前字符加入窗口; - 若当前字符
c已存在于seen集合中(说明窗口内有重复字符),则进入循环收缩左边界:- 移除左指针
left指向的字符(seen.remove(s.charAt(left))); - 左指针
left右移一位(left++); - 重复上述操作,直到
seen中不再包含c(此时窗口内恢复无重复状态)。
- 移除左指针
- 将当前字符
c加入seen集合(此时c是窗口新的右边界字符); - 计算当前窗口的长度(
right - left + 1),并更新maxLength为"当前最大值"和"窗口长度"中的较大者。
- 右指针
- 返回结果 :遍历完成后,
maxLength即为无重复字符的最长子串长度,直接返回。
核心优化逻辑说明
- 时间复杂度优化 :
- 暴力解法需枚举所有子串(O(n²)),并对每个子串检查是否有重复(O(n)),总时间复杂度O(n³),无法适配
n=5×10⁴的规模; - 滑动窗口仅需一次遍历(右指针遍历n次),左指针最多也移动n次,整体时间复杂度为O(n)(每个字符最多被左/右指针各访问一次),完全满足题目性能要求。
- 暴力解法需枚举所有子串(O(n²)),并对每个子串检查是否有重复(O(n)),总时间复杂度O(n³),无法适配
- 空间复杂度说明 :
- 该解法用
HashSet存储窗口内字符,最坏情况下(字符串无重复)需存储n个字符,空间复杂度为O(n),这是"时间换空间"的合理权衡; - 若需进一步优化空间,可改用数组(ASCII字符范围)替代
HashSet,但核心逻辑不变,仅存储方式不同。
- 该解法用
- 性能表现说明:
- 耗时5ms击败32.91%的用户:滑动窗口是该题的标准最优解,多数提交均采用此逻辑,耗时差异主要来自评测机运行环境(如JVM的即时编译、系统资源占用等),该耗时已属于高效范畴;
- 内存消耗76MB击败19.06%的用户:核心原因是
HashSet的底层存储(如HashMap的数组+链表/红黑树结构)存在额外内存开销,若改用数组存储字符是否存在,可降低内存消耗。
执行耗时:6 ms,击败了61.68% 的Java用户
内存消耗:45.9 MB,击败了13.31% 的Java用户
java
public int lengthOfLongestSubstring(String s) {
Set<Character> seen = new HashSet<>();
int left = 0, maxLength = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
while (seen.contains(c)) {
seen.remove(s.charAt(left));
left++;
}
seen.add(c);
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
总结
- 该解法的核心是滑动窗口动态维护无重复子串:右指针扩展窗口,左指针收缩窗口以消除重复,确保窗口内始终无重复字符,一次遍历即可找到最长长度;
HashSet的O(1)查找效率是滑动窗口能高效运行的关键,避免了每次检查重复都遍历窗口的冗余操作;- 滑动窗口的时间复杂度O(n)是该问题的最优下界,无法进一步优化,该解法已达到理论最优性能。