编码母题 / 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

解题思路与实现

方案一:字符串维护窗口

展开解题思路

用字符串 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

时间复杂度: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

时间复杂度:O(n)。每个码元只遍历一次,Map 查询和更新均按均摊 O(1) 计。

空间复杂度:O(min(n, Σ))。Map 最多记录输入中出现过的不同字符。

下载原始 JS 文件