算法记录-无重复字符的最长子串

5 阅读2分钟

题目

3.无重复字符的最长子串

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

思路

看到子串,特点是连续,一般先想到双指针,滑动窗口解法。 窗口内不能重复,发现重复必然是把左边删掉,使其重新合法。 那么两个指针始终往右走,这就符合了滑动窗口的特性。

实现

  • 初始化左指针指向头部,空字典记录字符最近出现位置。
  • 遍历右指针,字典判定字符重复出现且在滑动窗口内,就把左指针指向重复字符的下一位。
  • 不断更新字典中字符的下标。
  • 计算当前滑动窗口长度与历史值比较取最大值。循环结束后返回

题解

var lengthOfLongestSubstring = function(s) {
    let l = 0
    let res = 0
    let map = new Map()
    for(let r = 0; r < s.length; r++) {
        if(map.has(s[r]) && map.get(s[r]) >= l) {
            l = map.get(s[r]) + 1
        }
        map.set(s[r], r)
        res = Math.max(res, r - l + 1)
    }
    return res
};

复杂度

时间复杂度:O(n)

空间复杂度:O(n), 严格点可以是O(m),m是不同字符种类个数,本质就是map额外占用的空间

字典的作用

遇到重复字符时,帮助左指针直接跳到重复字符的后一位,而不是一个个移动

本质就是 空间换时间,空间复杂度 1 -> n, 时间复杂度 n^2 -> n

如果不用字典,那右指针每次移动,都需要遍历当前滑动窗口,查看是否有重复字符。


var lengthOfLongestSubstring = function(s) {
    let l = 0;
    let res = 0;
    for (let r = 0; r < s.length; r++) {
        // 在窗口 [l, r) 中找 s[r]
        for (let i = l; i < r; i++) {
            if (s[i] === s[r]) {
                l = i + 1;
                break;
            }
        }
        res = Math.max(res, r - l + 1);
    }
    return res;
};

优化

如果保证字符都是ASCII码,可以用 ASCII数组换map,因为数组下标访问比哈希更快

空间复杂度原则上就降到了O(1)

var lengthOfLongestSubstring = function(s) {
    const index = new Array(128).fill(-1);
    let l = 0;
    let res = 0;
    for (let r = 0; r < s.length; r++) {
        const code = s.charCodeAt(r);

        if (index[code] >= l) {
            l = index[code] + 1;
        }
        index[code] = r;
        res = Math.max(res, r - l + 1);
    }
    return res;
};