在算法刷题中,最长无重复字符子串是滑动窗口的经典入门题型,也是面试高频考点。很多新手容易陷入暴力枚举的超时陷阱,而滑动窗口(双指针)算法可以将这道题的时间复杂度优化到极致。今天结合完整可运行的 JS 代码,从零拆解解题思路、核心逻辑、代码细节和易错点,帮你彻底吃透这道题。
一、题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
注意:子串是字符串中连续的字符序列,区别于不连续的子序列,这是解题的关键前提。
示例:
- 输入:
s = "abcabcbb",输出:3,解释:最长无重复子串为"abc" - 输入:
s = "bbbbb",输出:1,解释:最长无重复子串为"b" - 输入:
s = "pwwkew",输出:3,解释:最长无重复子串为"wke"
二、解题思路:滑动窗口(双指针 + 哈希集合)
1. 暴力解法的弊端
最容易想到的思路是枚举所有子串,逐一判断是否有重复字符,记录最长长度。但这种方法的时间复杂度为 O(n²),遇到长字符串会直接超时,效率极低,无法通过题目测试用例。
2. 滑动窗口核心思想
滑动窗口是处理连续子数组/子串问题的最优解法之一,核心是用两个指针维护一个动态的窗口区间:
- 快指针(fast) :不断向右扩张窗口,遍历字符串每个字符,负责探索新字符
- 慢指针(slow) :当窗口内出现重复字符时,向右收缩窗口,剔除重复字符,保证窗口内始终无重复
- Set 集合:存储当前窗口内的字符,利用 Set 元素唯一的特性,快速判断字符是否重复
全程保持窗口 [slow, fast] 内的字符无任何重复,每次扩张窗口后更新最长子串长度,遍历完成后即可得到答案。
三、完整可运行代码
以下是优化后的原生 JS 解题代码,也是本文解析的核心代码,可直接在 LeetCode 提交通过:
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
// 定义快慢指针、最长子串长度结果
let fast = 0, slow = 0, ans = 0;
// Set存储当前窗口内的字符,自动去重、快速查重
let window = new Set();
// 快指针遍历整个字符串,扩张窗口
for(fast = 0; fast < s.length; fast++) {
// 窗口内存在当前字符,持续收缩窗口,直到无重复
while(window.has(s[fast])) {
window.delete(s[slow]);
slow++;
}
// 将当前字符加入窗口,维护无重复窗口
window.add(s[fast]);
// 更新最长子串长度
ans = Math.max(ans, fast - slow + 1);
}
return ans;
};
四、逐行代码详细解析
1. 变量初始化
let fast = 0, slow = 0, ans = 0;
let window = new Set();
fast:快指针,窗口右边界,逐个遍历字符串字符slow:慢指针,窗口左边界,控制窗口收缩范围ans:记录全局最长无重复子串的长度,初始为0window:Set 集合,存储当前窗口[slow, fast]内的所有字符,用于高效查重
2. 窗口扩张:快指针遍历
for(fast = 0; fast < s.length; fast++)
快指针从字符串首位遍历到末尾,每一次循环都将窗口向右扩张一位,逐个纳入新字符。
3. 窗口收缩:去除重复字符
while(window.has(s[fast])) {
window.delete(s[slow]);
slow++;
}
这是整道题的核心逻辑:
当即将加入窗口的 s[fast] 已经存在于当前窗口中,说明窗口出现重复字符,不满足题意。此时需要持续收缩左边界:删除慢指针指向的字符,慢指针右移,直到窗口中彻底不存在重复字符为止,保证窗口始终是合法的无重复区间。
区别于 if 判断,while 循环可以一次性清除所有前置重复字符,适配所有重复场景。
4. 更新窗口与结果
window.add(s[fast]);
ans = Math.max(ans, fast - slow + 1);
- 窗口合法后,将当前遍历的字符加入窗口
fast - slow + 1:计算当前窗口的字符长度(左右指针闭区间长度计算方式)- 每次遍历都对比当前窗口长度和历史最长长度,保留最大值
五、算法复杂度分析
时间复杂度:O(n)
字符串中每个字符只会被快慢指针各访问一次,不存在重复遍历,整体为线性时间复杂度,n 为字符串长度。
空间复杂度:O(1)
字符的取值范围是有限的(英文字母、数字、符号),Set 集合最多存储固定数量的字符,属于常数级空间,和字符串长度无关。
六、核心易错点总结
- 必须用 while 而非 if:如果只用 if 判断,只能收缩一次窗口,无法彻底清除前置重复字符,会导致窗口仍存在重复,结果错误。
- 窗口长度计算:左右指针为闭区间,必须
fast - slow + 1,少加1会导致长度统计偏小。 - 先收缩、后加字符:必须先清理重复字符,再将新字符加入窗口,顺序颠倒会导致重复字符被存入窗口。
七、解题总结
最长无重复子串是滑动窗口的标杆题型,核心解题模板可以总结为:右指针扩张探索,左指针收缩合规,容器存储窗口状态,实时更新最优解。
掌握这道题的逻辑后,可以快速迁移到最长子数组、最小覆盖子串、无重复子数组等一系列滑动窗口题型,是算法进阶的必备基础。