LeetCode 热题 100 之第8题 无重复字符的最长子串(JavaScript篇)

125 阅读4分钟

传送门:3. 无重复字符的最长子串 - 力扣(LeetCode)

🧩 题目:

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度。

✅ 示例:

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列, 不是子串。

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成

解题代码

/**
 * @param {string} s
 * @return {number} 
 */
var lengthOfLongestSubstring = function(s) {
   // 定义滑动窗口的左边界,初始化为 0
   let l = 0;
   // 记录最长无重复字符子串的长度,初始化为 0
   let max = 0;
   // 使用 Map 来存储字符及其最新出现的索引
   const map = new Map();
   // 定义滑动窗口的右边界,遍历整个字符串
   for (let r = 0; r < s.length; r++) {
       // 如果当前字符已经在 Map 中,并且其索引大于等于左边界
       if (map.has(s[r]) && map.get(s[r]) >= l) {
           // 将左边界移动到该字符上一次出现位置的下一个位置
           l = map.get(s[r]) + 1;
       }
       // 更新当前字符的最新出现索引
       map.set(s[r], r);
       // 计算当前窗口的长度,并更新最大长度
       max = Math.max(max, r - l + 1);
   }
   // 返回最长无重复字符子串的长度
   return max;
};

🎯 思路总结

我们来用一个“游戏”的方式理解这个算法:

  • 想象你在玩一个“找最长不重复字符串”的游戏。
  • 你有两个手指头:左手和右手,分别代表窗口的左右边界。
  • 右手从左往右慢慢移动,每碰到一个字母就记录下来。
  • 如果碰到一个已经记录过的字母,而且它在左手右边(说明它在窗口里),你就把左手向右移动一格,跳过这个重复的字符。
  • 每次移动后都算一下当前窗口有多长,并记录最大值。

这就是所谓的 滑动窗口 + 哈希表(Map) 算法。


🧱 逐行详细讲解代码

var lengthOfLongestSubstring = function(s) {

定义一个函数 lengthOfLongestSubstring,接收一个字符串 s

   let l = 0;
   let max = 0;
  • l 是 left 的缩写,表示窗口的左边界,初始为 0。
  • max 用来记录我们找到的最大长度,初始为 0。
   const map = new Map();

创建一个 Map,用来记录每个字符最后一次出现的位置。
比如 'a' 出现在第 2 位,就记录为 map.set('a', 2)

   for (let r = 0; r < s.length; r++) {

开始循环遍历字符串,r 是 right 的缩写,表示窗口的右边界,从 0 到字符串末尾。

       if (map.has(s[r]) && map.get(s[r]) >= l) {
           l = map.get(s[r]) + 1;
       }

如果当前字符 s[r] 已经出现过,并且它上一次出现的位置是在窗口里面(也就是大于等于 l),说明出现了重复字符。

  • 这时候要把窗口左边界 l 移动到 上一次出现的位置 + 1,这样就能跳过重复的那个字符。
  • 举个例子:字符串 "abba" 中,当第二个 b 出现时,就要把 l 移动到 b 上次出现的位置 + 1。
       map.set(s[r], r);

把当前字符和它的位置记录下来,更新到 map 中。

       max = Math.max(max, r - l + 1);

计算当前窗口的长度:r - l + 1,并更新最大值 max

   }
   return max;
};

循环结束后,max 就是我们要找的最长无重复字符子串的长度。


🧪 举个例子帮助理解:输入 "abcabcbb"

步骤字符当前窗口map 更新后max 值
0a[a]a:01
1b[ab]b:12
2c[abc]c:23
3a[bca]a:33
4b[cab]b:43
5c[abc]c:53
6b[cb]3
7b[b]3

最终结果是 3,正确!


🎯 总结一句话:

这段代码就像一个智能窗口,在字符串中不断向右滑动,遇到重复字符就自动调整窗口起点,同时记录窗口的最大长度。使用了一个 Map 来记住每个字符最后出现的位置,让整个过程非常高效。


💡 补充知识

showtime解释
时间复杂度O(n),每个字符最多被访问两次(left 和 right 各一次)
空间复杂度O(m),m 是字符集大小(比如英文字符最多 26/128 个)
为什么不用 Set?因为 Set 不知道字符上次出现在哪,不能快速定位窗口起点