题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 ****的长度。
示例:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。
提示:
0 <= s.length <= 5 * 104s由英文字母、数字、符号和空格组成
题解
滑动窗口
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function (s) {
// 哈希集合,记录每个字符是否出现过
const occ = new Set();
const n = s.length;
// 右指针,初始值为 -1
let r = -1, ans = 0;
for (let l = 0; l < n; l++) {
if (l != 0) {
// 左指针右移一格,移除原本的最开始字符
occ.delete(s.charAt(l - 1));
}
while (r + 1 < n && !occ.has(s.charAt(r + 1))) {
// 不断移动右指针
occ.add(s.charAt(r + 1));
++r;
}
ans = Math.max(ans, r - l + 1);
}
return ans;
};
整体思路:
通过左指针选取每一个字符作为起始点,然后while循环来扩展这个子串寻找最大不重复子串 即通过双指针实现一个"滑动窗口"
数据结构:
- 左指针用于指向子串的起始位置
- 右指针用于指向子串的终止位置,最初设置为-1,即指向子串左边界。
- 通过Set来实现查找新增字符与当前子串中字符是否重合
具体实现:
- 外层for循环用来实现逐一确定子串的起始位置,
- 再利用while循环实现右边界的拓展到最远
- 若当前拓展的元素有重复时,保存当前的最长子串的长度,进入到下一次for循环,以新的起点开始,并先删除上一个起点,如若还是有重复,继续更换新起点,删除旧起点。这样就以简单的代码实现了去掉重复的元素以及前面的字符这一操作。
- 直到遍历完所有的起点情况结束返回结果
时间,空间复杂度
时间复杂度:O(N)
虽然代码中有一个嵌套的 while 循环,但while循环内的操作并没有执行N^2次。
整个过程中,l从0到n-1 移动了 N 次,r 最多移动 N 次(也就是说,r要么是一直没有遇到重复的直接一次while循环移动到末尾,要么是有重复的字符,移动暂停然后直到执行了Set内的删除操作至没有重复元素再接着移动,最终算下来还是移动N次)。
l与r的移动是独立的
Set 的操作时间复杂度为 O(1)。 - 因此,总的时间复杂度是线性的 O(N)。
空间复杂度:O(∣Σ∣)
Σ 代表字符集的总大小。Set 中最多存储 O(∣Σ∣) 个不重复的字符。由于字符集大小是常数,因此空间复杂度是 O(1)。