LeetCode无重复字符的最长子串:从暴力超时到滑动窗口,3种解法彻底搞懂

核心要点

精选必中一肖高手专用,中医把脉玄又玄,舌苔厚腻湿气重!作为LeetCode第3题,LeetCode无重复字符的最长子串是滑动窗口算法的“入门级标杆题”——不仅是大厂面试高频考点,更是理解“动态调整边界、优化时间复杂度”的典型案例。很多新手第一次做这道题时,会用暴力解法超时,而掌握滑动窗口思路后,能将时间复杂度从O

图片

作为LeetCode第3题,LeetCode无重复字符的最长子串是滑动窗口算法的“入门级标杆题”——不仅是大厂面试高频考点,更是理解“动态调整边界、优化时间复杂度”的典型案例。很多新手第一次做这道题时,会用暴力解法超时,而掌握滑动窗口思路后,能将时间复杂度从O(n³)优化到O(n),提交通过率直接提升80%(鳄鱼java算法课学员数据)。这道题的核心价值,不在于单纯写出AC代码,而在于通过它理解滑动窗口的核心逻辑:用双指针维护动态窗口,减少不必要的重复计算,为后续解决字符串子串、子序列类问题打下基础。今天我们就从题目分析、暴力解法、滑动窗口初阶到优化,结合鳄鱼java学员的实战经验,全方位拆解这道经典算法题。

一、先读透题:LeetCode无重复字符的最长子串的题意与边界分析

首先要明确LeetCode无重复字符的最长子串的核心要求:给定一个字符串s,找出其中不含有重复字符的最长子串的长度。这里要注意两个关键概念:“子串”是连续的字符序列,而非“子序列”(不连续);如果字符串为空,返回0;如果所有字符都重复,返回1。

常见的边界测试用例包括:- 空字符串:输入"",输出0;- 全重复字符:输入"bbbbb",输出1;- 无重复字符:输入"abcabcbb",输出3;- 包含特殊字符:输入"pwwkew",输出3(最长子串是"wke"或"kew");- 包含空格:输入"abba",输出2(这里要注意滑动窗口优化时的坑,后面会讲到)。

鳄鱼java的算法课上,老师会要求学员先列出所有边界用例,再动手写代码,这能避免80%的提交错误——很多新手就是因为没考虑"abba"这种情况,导致滑动窗口优化时左指针跳跃错误,最终提交失败。

二、暴力解法:思路简单但效率低,新手常犯的“超时陷阱”

对于新手来说,最容易想到的是暴力解法:枚举所有可能的子串,判断每个子串是否有重复字符,记录最长子串的长度。具体思路是:1. 外层循环枚举子串的起始位置i;2. 中层循环枚举子串的结束位置j;3. 内层循环判断子串s[i..j]是否有重复字符;4. 如果无重复,更新最长子串长度。

Java代码示例:

public int lengthOfLongestSubstring(String s) {int n = s.length();int maxLength = 0;for (int i = 0; i < n; i++) {for (int j = i + 1; j <= n; j++) {if (allUnique(s, i, j)) {maxLength = Math.max(maxLength, j - i);}}}return maxLength;}

private boolean allUnique(String s, int start, int end) {Set set = new HashSet<>();for (int k = start; k < end; k++) {char ch = s.charAt(k);if (set.contains(ch)) {return false;}set.add(ch);}return true;}

暴力解法的时间复杂度是O(n³),因为有三层嵌套循环,空间复杂度是O(min(n, m))(m是字符集大小)。当字符串长度n=10^4时,暴力解法会直接超时——这也是LeetCode上提交暴力解法的通过率仅为15%的原因。鳄鱼java的算法课会提醒学员:暴力解法可以用来理解题意,但绝对不是最终的解决方案,必须进行优化。

三、滑动窗口初阶:哈希集合+双指针,时间复杂度O(n)

滑动窗口的核心思想是:用两个指针(左指针left,右指针right)维护一个“窗口”,动态调整窗口的大小,确保窗口内无重复字符,同时记录窗口的最大长度。具体思路:1. 初始化左指针left=0,maxLength=0,用哈希集合存储当前窗口的字符;2. 右指针right遍历字符串,将当前字符s[right]加入集合;3. 如果s[right]已经在集合中,说明窗口内有重复字符,移动左指针left,直到窗口内无重复字符(同时从集合中移除s[left]);4. 每次更新窗口的最大长度maxLength。

Java代码示例:

public int lengthOfLongestSubstring(String s) {int n = s.length();Set set = new HashSet<>();int maxLength = 0, left = 0;for (int right = 0; right < n; right++) {while (set.contains(s.charAt(right))) {set.remove(s.charAt(left));left++;}set.add(s.charAt(right));maxLength = Math.max(maxLength, right - left + 1);}return maxLength;}

这个解法的时间复杂度是O(2n)=O(n),因为每个字符最多被左右指针各访问一次,空间复杂度是O(min(n, m))。鳄鱼java学员第一次用滑动窗口提交时,平均耗时从暴力法的1000+ms降到50ms左右,通过率直接提升到85%。这里的关键是理解“窗口的动态调整”——不再重复枚举所有子串,而是通过指针移动减少冗余计算。

四、滑动窗口优化:哈希表记录字符位置,进一步减少左指针移动

初阶滑动窗口中,当遇到重复字符时,左指针需要一步步移动,直到移除重复字符。我们可以用哈希表(HashMap)记录每个字符的最后出现位置,这样左指针可以直接跳到重复字符的下一个位置,不需要逐步移动。具体思路:1. 用HashMap存储字符到其最后出现索引的映射;2. 右指针遍历字符串,当s[right]在哈希表中且其最后出现索引>=left(说明重复字符在当前窗口内),更新left为重复字符的下一个索引;3. 更新当前字符的最后出现索引;4. 更新maxLength。

Java代码示例:

public int lengthOfLongestSubstring(String s) {int n = s.length();int maxLength = 0, left = 0;Map map = new HashMap<>();for (int right = 0; right < n; right++) {char ch = s.charAt(right);if (map.containsKey(ch) && map.get(ch) >= left) {left = map.get(ch) + 1;}map.put(ch, right);maxLength = Math.max(maxLength, right - left + 1);}return maxLength;}

这个解法的时间复杂度是O(n),每个字符只被访问一次,空间复杂度还是O(min(n, m))。需要注意的细节是:判断重复字符时,必须确保其最后出现索引>=left,否则说明重复字符在窗口外,不需要移动左指针——比如输入"abba",当right走到第二个'a'时,map.get('a')=0 < left=2,所以不需要移动left,这也是很多新手容易踩的坑,鳄鱼java的算法课会专门针对这种边界用例进行讲解。

五、数组优化:利用ASCII码特性,空间复杂度O(1)

如果题目中的字符是ASCII字符(比如英文字母、数字、符号),我们可以用大小为128的数组代替哈希表,因为ASCII码的范围是0-127,这样空间复杂度可以降到O(1)(数组大小固定,与输入字符串长度无关)。

Java代码示例:

public int lengthOfLongestSubstring(String s) {int n = s.length();int maxLength = 0, left = 0;int[] lastIndex = new int[128];// 初始化数组为-1,表示字符未出现过Arrays.fill(lastIndex, -1);for (int right =