LeetCode003 无重复字符的最长子串

1 数组实现

维护一个数组,总是记录无重复子串; 维护一个变量,总是记录当前无重复子串的长度中较长的那个
有重复,就删除当前记录重复子串中导致重复的部分子串0~start。
每次遍历在记录长度时,和当前记录值比较,取较大的那个。
最终记录的是最长子串长度

const lengthOfLongestSubstring = s => {
    if (typeof s !== 'string') throw 'TypeError'
    const arr = []
    return s.split('').reduce((max, cur) => {
        const start = arr.indexOf(cur)
        if (start !== -1) {
            arr.splice(0, start + 1) // 有重复,删除当前记录重复子串中导致重复的部分子串, 如'abca', 则删除第一位'a'
        }
        arr.push(cur)
        return Math.max(max, arr.length)
    }, 0)
}

2 滑动窗口

窗口为start, end. 在j处发现有重复值,则将窗口start移动到j+1的位置,毕竟从后一位才开始不重复。
窗口长度(end - start +1) 即为无重复最长子串长度

const lengthOfLongestSubstring = s => {
    if (typeof s !== 'string') throw 'TypeError'
    let max = 0
    for (let start = 0, j = 0; j < s.length; j++) {
        const index = s.substring(start, j).indexOf(s[j])
        if (index !== -1) {
            start = start + index + 1 // substring一直在子串里查找,不会出现重复父串嵌套重复子串的case
        }
        max = Math.max(max, j - start + 1)
    }
    return max
}

3 滑动窗口优化

利用map优化indexOf的查找过程,使得时间复杂度变为O(n)
map方式没有截去之前的重复子串,对于相同字符,只会存储较大的那个字符索引
因此在重复父串嵌套重复子串的情况时,要按子串计算, 即设置窗口start的时候要和自身对比取max

const lengthOfLongestSubstring = s => {
    if (typeof s !== 'string') throw 'TypeError'
    let max = 0
    const map = new Map()
    for (let start = 0, j = 0; j < s.length; j++) {
        const cur = s[j]
        if (map.get(cur) !== undefined) {
            // +1表示后一位开始才不重复, max表示如果重复父串中包含重复子串,应该按子串计算,如果按父串计算,那么父串会包含重复子串,不符合条件。
            // 例如tmmzuxt,父串t~t嵌套了子串m~m, 如果不取max, 那么start最终为1, 显然不对
            start = Math.max(map.get(cur) + 1, start)
        }
        max = Math.max(max, j - start + 1)
        map.set(cur, j)
    }
    return max
}
posted @ 2022-06-29 16:28  IslandZzzz  阅读(37)  评论(0编辑  收藏  举报