题目
给定一个字符串 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;
};