小哆啦解题记:最长无重复子串

123 阅读4分钟

小哆啦解题记:最长无重复子串

小哆啦开始刷力扣的第二十六天

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

今天,小哆啦遇到了一道题—— “最长无重复子串” 。一看题目,小哆啦顿时皱起了眉头:“哎呀,这个看起来简单,实际操作起来肯定不是那么一回事!”她习惯性地往旁边看了一眼,果然——小智正在一旁看着她,准备发挥作用。

小智一脸严肃地看着屏幕,仿佛是个机智的老师:“你看,这道题我们可以先暴力解一下,虽然很慢,但能先把思路理清楚,行不行?”

小哆啦点点头:“嗯,暴力解法总是可以的!”

于是,小哆啦开始了她的暴力“战争”。她从字符串的每个位置出发,尝试找出所有的子串,逐个判断是否包含重复字符。结果……她发现这方法虽然能解题,但效率极其低下,连计算机都快喊累了!


暴力解法:硬刚所有子串

小哆啦猛地敲击键盘,想法还没完全理清,她就已经输入了如下代码:

function lengthOfLongestSubstring(s: string): number {
    let maxLength = 0;
    for (let i = 0; i < s.length; i++) {
        for (let j = i + 1; j <= s.length; j++) {
            let subStr = s.slice(i, j);
            if (new Set(subStr).size === subStr.length) {
                maxLength = Math.max(maxLength, subStr.length);
            }
        }
    }
    return maxLength;
}

她满意地看着屏幕,心里还暗自得意:“这应该能解决问题吧?”可结果一运行,计算机发出一阵低沉的呻吟——时间复杂度 O(n²) ,直接让她陷入了深深的惆怅。

这时,小智悄悄凑过来,咳嗽一声:“呃……小哆啦,你知道吗,这样的方法效率实在是太低了,简直像是在暴走前进。是时候换个聪明的办法了。”


使用 Map 和数组:稍微聪明一点

小哆啦听到小智的提醒后,点了点头,心想:“好吧,既然暴力不行,那我们得想点新招。”于是她决定换个思路,使用 Map 来记录字符的位置,避免不必要的重复计算。

小智笑了笑:“对,这样我们就能避免每次都从头检查。直接记录字符位置,保证我们每次跳到最新的有效位置!”

小哆啦一脸信心地开始编写新代码:

function lengthOfLongestSubstring(s: string): number {
    let strMap = new Map();
    let maxLength = 0;
    let strArr: string[] = [];
    
    for (let i = 0; i < s.length; i++) {
        if (!strMap.has(s[i])) {
            strArr.push(s[i]);
            strMap.set(s[i], i); // 记录字符的最新位置
        } else {
            let index = strArr.indexOf(s[i]);
            strArr = strArr.slice(index + 1); // 删掉重复字符之前的所有元素
            strArr.push(s[i]);
            strMap.clear();
            for (let j = 0; j < strArr.length; j++) {
                strMap.set(strArr[j], j); // 重新更新 map
            }
        }
        maxLength = Math.max(maxLength, strArr.length);
    }
    
    return maxLength;
}

她运行了代码,看着屏幕的输出,她终于稍微松了一口气:“还行,至少快了一些。”

但是,小智并不满足:“这还行?你看到它的复杂度了吗?虽然比暴力解法好,但空间复杂度和时间复杂度还有点高。要是你能再优化一点,岂不是更牛逼?”

小哆啦愣住了:“嗯?还有更好的方法?那快说说!”


双指针 + Map:光速解决

小智拍了拍小哆啦的肩膀:“好了,现在我来教你‘光速’解法,双指针加 Map,看着!”

小哆啦用崇拜的眼神盯着小智:“哦!快点,快点,给我指点一下!”

“我们用两个指针,” 小智开始详细讲解,“一个指针负责扩展窗口,另一个指针负责收缩窗口,确保没有重复字符。这样既能保证我们一直保持最长的无重复子串,又能把时间复杂度降到最优!”

小哆啦眼前一亮:“好,双指针,Map!这种方法太酷了!”

她飞速打出了以下代码:

function lengthOfLongestSubstring(s: string): number {
    let strMap = new Map<string, number>();
    let maxLength = 0;
    let left = 0; // 滑动窗口的左指针

    for (let right = 0; right < s.length; right++) {
        if (strMap.has(s[right])) {
            // 更新左指针,避免回退
            left = Math.max(left, strMap.get(s[right])! + 1);
        }
        // 记录字符最新位置
        strMap.set(s[right], right);
        // 计算当前无重复子串长度
        maxLength = Math.max(maxLength, right - left + 1);
    }

    return maxLength;
}

她运行完之后,结果闪电般出来,心中一阵欣喜:“太快了!这个速度简直就是飞车模式!”

小智满意地拍了拍小哆啦的背:“看吧,这才是最牛的解法,时间复杂度 O(n) ,而且空间复杂度也不会浪费。以后遇到这种题,你就知道该怎么做了!”

小哆啦深深地点了点头,终于明白了优化的重要性:“双指针加 Map,真的是解题的终极武器!”


总结

这道题,给小哆啦带来了不少挑战。从暴力解法的低效,到使用 Map 和数组的优化,再到最后的双指针加 Map,小哆啦完成了一个从无知到知晓的进阶过程。而小智,作为她的良师益友,也在背后给她指点迷津。

小哆啦拍了拍自己的脑袋,心中默默发誓:“以后做题,我一定要考虑更高效的方法,暴力解法,真的不行!”