给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串(子字符串 是字符串中连续的 非空 字符序列。) 的长度。
示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
题解1 暴力解法
思路
其实暴力解法最重要的就是读懂题目,题目想找出最长的子串,可以枚举每个字符为起点,往后枚举他的止点,顺便更新下答案。如果遇见相同字符,那么退出这个字符的枚举。
为了避免重复计算,如果从 i 开始往后都没有已知结果大,那么可以提前 return。
代码
function lengthOfLongestSubstring(s: string): number {
const set: Set<string> = new Set();
let result: number = 0;
for (let i = 0; i < s.length; i++) {
for (let j = i; j < s.length; j++) {
if (set.has(s[j])) {
set.clear();
break;
} else {
set.add(s[j]);
result = Math.max(result, set.size);
}
}
if (s.length - i < result) { // 剪一下枝 避免重复计算
return result;
}
set.clear();
}
return result;
};
时空复杂度分析
时间复杂度:外层循环 O(n),内层循环最坏循环 O(n)。 总的复杂度为 O(n^2)
空间复杂度:使用了 set 来缓存已存在的字符串,复杂度为 O(n)
题解2 滑动窗口
思路
暴力解法的问题在于:
从 i 到 j 的子串如果有重复字符,那么会从 i + 1 到 j 重复进行遍历,而可能还是遍历到 j 遇到重复字符。它的下一次遍历并不保证会排除重复字符,所以造成了很多重复计算。
优化它的方式就是如果遇到重复子串,那么先把那个重复的字符给清空出去,然后再继续遍历。
有两种方式,一是用数组来维护,可以直接 shift(),二是用变量来维护。
代码
// 1.数组维护
function lengthOfLongestSubstring(s: string): number {
const list: string[] = [];
let result: number = 0;
for (let char of s) {
while (list.includes(char)) {
list.shift();
}
list.push(char);
result = Math.max(list.length, result);
}
return result;
};
// 2.变量维护
function lengthOfLongestSubstring(s: string): number {
const map: Object = {};
let result: number = 0;
let left: number = 0;
let right: number = 0;
while (right < s.length) {
if (map[s[right]]) {
delete map[s[left]];
left += 1;
} else {
map[s[right]] = 1;
result = Math.max(result, right - left + 1);
right += 1;
}
}
return result;
};
时空复杂度分析
时间复杂度:数组维护最坏可能为 O(n^2),用变量维护为 O(n)
空间复杂度:因为要缓存当前维护的无重复子串,所有复杂度为 O(n)