小哆啦解题记:最长无重复子串
小哆啦开始刷力扣的第二十六天
今天,小哆啦遇到了一道题—— “最长无重复子串” 。一看题目,小哆啦顿时皱起了眉头:“哎呀,这个看起来简单,实际操作起来肯定不是那么一回事!”她习惯性地往旁边看了一眼,果然——小智正在一旁看着她,准备发挥作用。
小智一脸严肃地看着屏幕,仿佛是个机智的老师:“你看,这道题我们可以先暴力解一下,虽然很慢,但能先把思路理清楚,行不行?”
小哆啦点点头:“嗯,暴力解法总是可以的!”
于是,小哆啦开始了她的暴力“战争”。她从字符串的每个位置出发,尝试找出所有的子串,逐个判断是否包含重复字符。结果……她发现这方法虽然能解题,但效率极其低下,连计算机都快喊累了!
暴力解法:硬刚所有子串
小哆啦猛地敲击键盘,想法还没完全理清,她就已经输入了如下代码:
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,小哆啦完成了一个从无知到知晓的进阶过程。而小智,作为她的良师益友,也在背后给她指点迷津。
小哆啦拍了拍自己的脑袋,心中默默发誓:“以后做题,我一定要考虑更高效的方法,暴力解法,真的不行!”