1、题目描述
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例1:
输入:“abcabcbb”
输出:3
解释:因为无重复字符的最长子串是“abc”,所以其长度为`3`。
示例 2:
输入:“bbbbb”
输出:1
解释:因为无重复字符的最长子串是“b”,所以其长度为`1`。
示例 3:
输入:“pwwkew”
输出:3
解释:因为无重复字符的最长子串是“wke”,所以其长度为`3`。
请注意,你的答案必须是 子串 的长度,“pwke”是一个子序列,不是子串。
2、解题思路
这个题目可以使用双指针+map来实现:
- 首先用双指针维护一个滑动窗口用来剪切子串。
- 开始时,两个指针都在起始位置,不断移动右指针,遇到重复的字符,就将左指针向后移动一位。
- 右指针每次移动,都计算出两个指针之间的字符个数,并返回最大值。
- 每次右指针移动还需要将右指针的索引和值存在Map中,便于后面遇到重复值时让左指针进行移动。
需要注意的是,在左指针移动之后,map中还存在其之前的值,所以还要限制map中已经存在的值的索引要大于左指针的索引,也就是必须处于两指针之间的滑动窗口。
该算法的时间复杂度为O(n),空间复杂度为O(m),其中m是最长子串的长度。
3、代码实现
/**
*
*/
var lengthOfLongestSubstring = function(s) {
let res = 0
let map = {}
for(let left = 0, right = 0; right < s.length; right++) {
const char = s[right]
if (map[char] >= 0 && map[char] >= left) {
left = map[char] + 1
}
res = Math.max(res, right - left + 1)
map[char] = right
}
return res
};
滑动窗口 - > “无重复字符的最长子串”
思路:
1、滑动窗口:维护一个窗口[left, right], 表示当前考察的子串。
2、哈希记录:用对象map记录每个字符最后出现的位置。
3、窗口调整:遇到重复字符时,移动左边界到重复字符的下一个位置。
代码逐行解析:
var lengthOfLongestSubstring = function(s) {
let res = 0; // 存储最长子串
let map = {}; // 记录字符最后出现位置的哈希表
// 滑动窗口:left是窗口左边界,right是右边界
for (let left = 0; right = 0; right < s.length; right++) {
const char = s[right]; // 当前右边界字符
// 如果字符已存在且在当前窗口内(map[char] >= left)
if (map[char] >= 0 && map[char] >= left) {
left = map[char] + 1; // 移动左边界到重复字符的下一个位置
}
// 更新最大长度(当前窗口大小:right-left+1)
res = Math.max(res, right - left + 1);
// 记录/更新当前字符的位置
map[char] = right;
}
return res; // 返回找到的最大长度
};
示例分析:
输入“abcabcbb”的执行过程:
1、right = 0 (a): map = {a: 0}, res = 1
2、right = 1 (b): map = {a: 0, b: 1}, res = 2
3、right = 2 (c): map = {a: 0, b: 1, c: 2}, res = 3
4、right = 3 (a): 发现重复, left=1 -> res保持3
5、right = 4 (b): 发现重复, left=2 -> res保持3
6、right = 5 (c): 发现重复, left=3 -> res保持3
7、right = 6 (c): 发现重复, left=5 -> res=3
8、right = 7 (b): 发现重复, left=7 -> res=3
最终返回3("abc")
复杂度分析:
-
时间复杂度:O(n),只需遍历一次字符串。
-
空间复杂度:O(min(m, n)), m是字符集大小