3_无重复字符的最长子串(算法思路,优化和复杂度分析)

80 阅读3分钟

题目描述

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

示例 1:

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

示例 2:

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

示例 3:

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

思路

通过维护一个动态窗口来跟踪当前不含重复字符的最长子串。

  1. 初始化
    • 使用一个 Map(映射)来记录字符及其最新出现的位置。
    • 设置两个指针 start 和当前字符位置 i,以及一个变量 count 来记录最长子串的长度。
  2. 遍历字符串
    • 对于字符串中的每一个字符 s[i],检查它是否已经存在于 Map 中。
    • 如果存在,更新 start 指针为当前记录的位置加一,以确保窗口内不含重复字符。更新时使用 Math.max 是为了避免 start 指针向回移动,确保窗口始终向前推进。
    • 将当前字符和它的位置 i 存入 Map,覆盖之前的位置记录。
    • 计算当前窗口的长度 i - start + 1,并更新 count 为当前窗口长度与之前记录的最大长度之间的较大值。
  3. 返回结果
    • 遍历完整个字符串后,count 即为最长不含重复字符的子串的长度。

注意点

为什么使用 Math.max(start, map.get(s[i]) + 1)

当我们遇到一个已经存在于当前窗口内的字符时,需要更新窗口的起始位置 start,以移除之前的重复字符。具体来说:

  1. 找到重复字符的位置map.get(s[i]) 返回字符 s[i] 上一次出现的位置。
  2. 更新 start:将 start 更新为 map.get(s[i]) + 1,即重复字符的下一个位置,确保窗口内没有重复。
  3. 使用 Math.max:在某些情况下,start 可能已经比 map.get(s[i]) + 1 更靠前了。为了避免 start 回退(因为当前的重复字符在窗口之外),我们取两者中的较大值。
    • 比如:“abba”,当遍历到第二个b的时候,start为0, map.get(s[i]) + 1为2,start在前面
    • 遍历到第二个a的时候,start = 2, map.get(s[i]) + 1 = 1, start在后面
执行过程

以下是字符串 "abba" 的执行过程:

索引字符startmapcount
0'a'0{ 'a': 0 }1
1'b'0{ 'a': 0, 'b': 1 }2
2'b'2{ 'a': 0, 'b': 2 }2
3'a'2{ 'a': 3, 'b': 2 }2

复杂度分析

  • 时间复杂度O(n)
  • 空间复杂度O(n)

code

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    const map = new Map()
    let start = 0
    let count = 0
    for(let i = 0; i < s.length; i++){
        if(map.has(s[i])){
            start = Math.max(start, map.get(s[i]) + 1)
        }
        map.set(s[i], i)
        count = Math.max(count, i - start + 1)
    }
    return count
};