编码母题 / P0 / 经典算法 / 01
无重复字符的最长子串
重要程度必练难度中等预计完成25–35 分钟
题目是什么
输入一个字符串,返回其中最长的无重复字符连续片段的长度。子串必须连续;重复字符出现时只丢弃必要的左侧部分,空串返回 0。两种实现按 JavaScript 字符串下标处理 UTF-16 码元,普通 emoji 不在本题验证范围。
输入输出示例
输入 abcabcbb,输出 3,对应 abc;输入 abba,输出 2。
练习起手式与验证
/*
* 题目:无重复字符的最长子串
* 要求:输入一个字符串,返回其中最长的无重复字符连续片段的长度。子串必须连续;重复字符出现时只丢弃必要的左侧部分,空串返回 0。两种实现按 JavaScript 字符串下标处理 UTF-16 码元,普通 emoji 不在本题验证范围。
* 示例:输入 abcabcbb,输出 3,对应 abc;输入 abba,输出 2。
*/
// 方案一
const lengthOfLongestSubstring1 = (str) => {};
console.log(lengthOfLongestSubstring1("")); // 预期:0
console.log(lengthOfLongestSubstring1("abcabcbb")); // 预期:3
console.log(lengthOfLongestSubstring1("abba")); // 预期:2
console.log(lengthOfLongestSubstring1("pwwkew")); // 预期:3
// 方案二
const lengthOfLongestSubstring2 = (str) => {};
console.log(lengthOfLongestSubstring2("")); // 预期:0
console.log(lengthOfLongestSubstring2("abcabcbb")); // 预期:3
console.log(lengthOfLongestSubstring2("abba")); // 预期:2
console.log(lengthOfLongestSubstring2("pwwkew")); // 预期:3
/*
* 题目:无重复字符的最长子串
* 要求:输入一个字符串,返回其中最长的无重复字符连续片段的长度。子串必须连续;重复字符出现时只丢弃必要的左侧部分,空串返回 0。两种实现按 JavaScript 字符串下标处理 UTF-16 码元,普通 emoji 不在本题验证范围。
* 示例:输入 abcabcbb,输出 3,对应 abc;输入 abba,输出 2。
*/
// 方案一
const lengthOfLongestSubstring1 = (str) => {};
console.log(lengthOfLongestSubstring1("")); // 预期:0
console.log(lengthOfLongestSubstring1("abcabcbb")); // 预期:3
console.log(lengthOfLongestSubstring1("abba")); // 预期:2
console.log(lengthOfLongestSubstring1("pwwkew")); // 预期:3
// 方案二
const lengthOfLongestSubstring2 = (str) => {};
console.log(lengthOfLongestSubstring2("")); // 预期:0
console.log(lengthOfLongestSubstring2("abcabcbb")); // 预期:3
console.log(lengthOfLongestSubstring2("abba")); // 预期:2
console.log(lengthOfLongestSubstring2("pwwkew")); // 预期:3解题思路与实现
方案一:字符串维护窗口
展开解题思路
用字符串 temp 保存无重复窗口;遇到重复字符时先记录当前窗口长度,再删去旧字符及其左侧部分,加入当前字符。
const lengthOfLongestSubstring = (str) => {
let temp = "";
let max = 0;
for (let i = 0; i < str.length; i++) {
const part = str[i];
const matchIndex = temp.indexOf(part);
if (matchIndex === -1) {
temp += part;
max = Math.max(max, temp.length);
} else {
max = Math.max(max, temp.length);
temp = temp.slice(matchIndex + 1) + part;
}
}
return max;
};
console.log(lengthOfLongestSubstring("")); // 预期:0
console.log(lengthOfLongestSubstring("abcabcbb")); // 预期:3
console.log(lengthOfLongestSubstring("abba")); // 预期:2
console.log(lengthOfLongestSubstring("pwwkew")); // 预期:3
const lengthOfLongestSubstring = (str) => {
let temp = "";
let max = 0;
for (let i = 0; i < str.length; i++) {
const part = str[i];
const matchIndex = temp.indexOf(part);
if (matchIndex === -1) {
temp += part;
max = Math.max(max, temp.length);
} else {
max = Math.max(max, temp.length);
temp = temp.slice(matchIndex + 1) + part;
}
}
return max;
};
console.log(lengthOfLongestSubstring("")); // 预期:0
console.log(lengthOfLongestSubstring("abcabcbb")); // 预期:3
console.log(lengthOfLongestSubstring("abba")); // 预期:2
console.log(lengthOfLongestSubstring("pwwkew")); // 预期:3时间复杂度:O(n²)。每个字符都可能触发最长 O(n) 的 indexOf、slice 和字符串拼接。
空间复杂度:O(min(n, Σ))。temp 最多保存所有不同字符,Σ 为字符集大小。
方案二:Map 记录最近位置
展开解题思路
Map 记录字符最近出现的下标;只有上次位置仍在窗口内,才把左边界移到它的后一位。
const lengthOfLongestSubstring = (str) => {
let map = new Map(); // 字符 → 最新出现的索引
let max = 0;
let left = 0; // 窗口左边界(闭区间)
for (let right = 0; right < str.length; right++) {
const part = str[right];
// 关键①:仅当字符存在且索引在窗口内,才移动左边界(处理两个相邻 且 相等的字符)
// 关键②:>= left 因为可能是第一项
if (map.has(part) && map.get(part) >= left) {
left = map.get(part) + 1;
}
map.set(part, right); // 更新字符最新索引
max = Math.max(max, right - left + 1);
}
return max;
};
console.log(lengthOfLongestSubstring("")); // 预期:0
console.log(lengthOfLongestSubstring("abcabcbb")); // 预期:3
console.log(lengthOfLongestSubstring("abba")); // 预期:2
console.log(lengthOfLongestSubstring("pwwkew")); // 预期:3
const lengthOfLongestSubstring = (str) => {
let map = new Map(); // 字符 → 最新出现的索引
let max = 0;
let left = 0; // 窗口左边界(闭区间)
for (let right = 0; right < str.length; right++) {
const part = str[right];
// 关键①:仅当字符存在且索引在窗口内,才移动左边界(处理两个相邻 且 相等的字符)
// 关键②:>= left 因为可能是第一项
if (map.has(part) && map.get(part) >= left) {
left = map.get(part) + 1;
}
map.set(part, right); // 更新字符最新索引
max = Math.max(max, right - left + 1);
}
return max;
};
console.log(lengthOfLongestSubstring("")); // 预期:0
console.log(lengthOfLongestSubstring("abcabcbb")); // 预期:3
console.log(lengthOfLongestSubstring("abba")); // 预期:2
console.log(lengthOfLongestSubstring("pwwkew")); // 预期:3时间复杂度:O(n)。每个码元只遍历一次,Map 查询和更新均按均摊 O(1) 计。
空间复杂度:O(min(n, Σ))。Map 最多记录输入中出现过的不同字符。