题目
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例1
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例2
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例3
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示
- 0 <= s.length <= 5 * 104
- s 由英文字母、数字、符号和空格组成
分析
我们想象一下,对于一个字符串,比如示例1中的 abcabcbb
,从他的起始位置开始,也就是字符 a
,向右遍历。遍历至第二个位置,也就是字符 b
时,他和他前面的字符 a
构成的这个子串 ab
,是不含有重复字符的。假设有一个容器,我们把这两个字符构成的子串放在这个容器里,接着继续遍历。至第三个字符 c
时,我们的这个容器里不含有和 c
重复的字符,所以把字符 c
放入这个容器,此时容器里就是由 a
, b
, c
这三个字符构成的子串。
继续遍历至第四个字符 a
,我们发现容器里包含了字符 a
,那么当前容器里的子串就是目前的最长子串。显然我们不能停在这里,也许后面的字符里会出现更长的无重复字符子串。可以看出来,如果去掉容器里重复的字符 a
,剩余的字符 b
, c
和当前的字符 a
构成了一个新的无重复字符的子串。
再向后遍历,第五个字符 b
已经在容器中了,跟上述的情况一样,我们可以判断当前容器里的最长子串和之前的最长子串哪一个更长。接着去除容器中重复字符和他之前的字符,并把新的字符放入容器中,构成新的子串。继续遍历。
总结一下,我们有一个容器,每遇到一个新的字符,判断容器中是否存在这个字符,如果存在,就把这个字符及其之前的字符从容器中去除,并放入这个新的字符,直至遍历结束。可以看出,这个容器的长度始终在变化,他的右侧不断放入新的字符,在遇到重复字符时,将其中的重复字符及其左侧的字符去除,同时这个容器也在不断的右移,这样的容器就是一个滑动窗口。
解答
impl Solution {
pub fn length_of_longest_substring(s: String) -> i32 {
// 记录最长子串的长度
let mut max = 0;
// 上文中的容器
let mut set = std::collections::HashSet::new();
let s_chars: Vec<_> = s.chars().collect();
// 容器左右两侧的索引位置
let (mut left, mut right) = (0, 0);
while right < s_chars.len() {
let ch = s_chars.get(right).unwrap();
// 判断当前字符是否在容器中
if set.contains(ch) {
max = std::cmp::max(max, right - left);
// 去除容器中重复字符及其左侧的字符
while s_chars.get(left).unwrap() != ch {
set.remove(s_chars.get(left).unwrap());
left += 1;
}
set.remove(s_chars.get(left).unwrap());
left += 1;
}
// 将新的字符放入容器中
set.insert(ch);
right += 1;
}
std::cmp::max(max, right - left) as i32
}
}