题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路
通过维护一个动态窗口来跟踪当前不含重复字符的最长子串。
- 初始化:
- 使用一个
Map(映射)来记录字符及其最新出现的位置。 - 设置两个指针
start和当前字符位置i,以及一个变量count来记录最长子串的长度。
- 使用一个
- 遍历字符串:
- 对于字符串中的每一个字符
s[i],检查它是否已经存在于Map中。 - 如果存在,更新
start指针为当前记录的位置加一,以确保窗口内不含重复字符。更新时使用Math.max是为了避免start指针向回移动,确保窗口始终向前推进。 - 将当前字符和它的位置
i存入Map,覆盖之前的位置记录。 - 计算当前窗口的长度
i - start + 1,并更新count为当前窗口长度与之前记录的最大长度之间的较大值。
- 对于字符串中的每一个字符
- 返回结果:
- 遍历完整个字符串后,
count即为最长不含重复字符的子串的长度。
- 遍历完整个字符串后,
注意点
为什么使用 Math.max(start, map.get(s[i]) + 1)
当我们遇到一个已经存在于当前窗口内的字符时,需要更新窗口的起始位置 start,以移除之前的重复字符。具体来说:
- 找到重复字符的位置:
map.get(s[i])返回字符s[i]上一次出现的位置。 - 更新
start:将start更新为map.get(s[i]) + 1,即重复字符的下一个位置,确保窗口内没有重复。 - 使用
Math.max:在某些情况下,start可能已经比map.get(s[i]) + 1更靠前了。为了避免start回退(因为当前的重复字符在窗口之外),我们取两者中的较大值。- 比如:“abba”,当遍历到第二个b的时候,start为0, map.get(s[i]) + 1为2,start在前面
- 遍历到第二个a的时候,start = 2, map.get(s[i]) + 1 = 1, start在后面
执行过程
以下是字符串 "abba" 的执行过程:
| 索引 | 字符 | start | map | count |
|---|---|---|---|---|
| 0 | 'a' | 0 | { 'a': 0 } | 1 |
| 1 | 'b' | 0 | { 'a': 0, 'b': 1 } | 2 |
| 2 | 'b' | 2 | { 'a': 0, 'b': 2 } | 2 |
| 3 | 'a' | 2 | { 'a': 3, 'b': 2 } | 2 |
复杂度分析
- 时间复杂度:
O(n) - 空间复杂度:
O(n)
code
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
const map = new Map()
let start = 0
let count = 0
for(let i = 0; i < s.length; i++){
if(map.has(s[i])){
start = Math.max(start, map.get(s[i]) + 1)
}
map.set(s[i], i)
count = Math.max(count, i - start + 1)
}
return count
};