一.题目描述
3. 无重复字符的最长子串https://leetcode.cn/problems/longest-substring-without-repeating-characters/
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2:
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为3。请注意,你的答案必须是 子串 的长度,"pwke"是一个子序列,不是子串。提示:
0 <= s.length <= 5 * 10^4
s
由英文字母、数字、符号和空格组成
二.解题思路
1.滑动窗口
总结题目意思,在字符串中求区间值,一般都是通过滑动窗口解决。抽象案例,每一个子串的左端点和右端点都是大致不断向右移动的。所以我们采取滑动窗口加哈希表解决。
以 (a)bcabcbb 开始的最长字符串为 (abc)abcbb
以 a(b)cabcbb 开始的最长字符串为 a(bca)bcbb;
以 ab(c)abcbb 开始的最长字符串为 ab(cab)cbb;
以 abc(a)bcbb 开始的最长字符串为 abc(abc)bb
以 abca(b)cbb 开始的最长字符串为 abca(bc)bb;
以 abcab(c)bb 开始的最长字符串为 abcab(cb)b ;
以 abcabc(b)b 开始的最长字符串为 abcabc(b)b;
以 abcabcb(b) 开始的最长字符串为 abcabcb(b)
我们定义一个哈希表来记录该字符是否出现过,用left来记录子串的左端点。
遍历字符串 "pwwkew"
1 .遇见未出现过的字符,添加到哈希表中
2.遇到出现过的字符,从头部依次退出字符,直到该字符重复元素被弹出。
完整代码
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//哈希表记录元素是否重复
unordered_set<char> hash;
int n = s.size();
int maxlen = 0;
int left = 0;
for(int i = 0;i < n;i++){
//未出现,添加到hash
if(!hash.count(s[i])) hash.insert(s[i]);
else {
//已出现,弹出
while(hash.count(s[i])) hash.erase(s[left++]);
hash.insert(s[i]);
}
int len = hash.size();
maxlen = max(maxlen, len);
}
return maxlen;
}
};
2.优化
左指针挨个移动效率太低,哈希表不断删除,这里有许多无效循环。我们可以记录字符上一次出现的位置,如果出现过就把左指针移动到max(left, 字符上一次出现的位置+1)
好的了解了优化方向后,我们来重新梳理一下优化思路:
首先在了解题目大概意思后,我们很容易想到用一个哈希表来判断该字符是否重复。根据ASCLL表设定哈希表大小为128即可。初始化为-1表示该字符暂未出现。
vector<int> hash(128, -1);
在设定完哈希表后,又如何确定子串的长度呢。我们现在只用下标是已知的。已知下标求长度,那就很容易想到下标相减为长度。所以我们需要知道重复元素的下标,刚好使用了哈希表,可以用哈希表存储一下重复元素的下标。
hash[ s[i] ] = i
但子串长度真的只是重复元素的下标相减就行吗?
"abcbac"
cb子串长度为第2个b的小标减去第1个b的下标 3 - 1 = 2
cba子串长度为第2个a的下标减去该子串开头前一个元素的下标既第一个b的下标4 - 1 = 3
根据上面案例的比较,我们应该取这两种情况的最小值为子串的长度。这里我们新定义sigh来确定子串的开头前一个元素的下标。
len = min(i - hash[s[i]], i -sigh);
sigh = max(hash[s[i]], sigh);
自此每个子串的长度我们就求出来了。现在只需要定义应该maxLen来在众多len中找到最大值返回即可。
全部代码
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
//定义哈希表来确定元素重复
vector<int> hash(128, -1);
int len = 0;
int maxlen = 0;
//sigh标记子串的开头前一个元素的下标
int sigh = 0;
for(int i = 0;i < n;i++){
//该元素为出现过,子串长度+1
if(hash[s[i]] == -1) len++;
else {
//元素重复,重新确定子串长度和sigh
len = min(i - hash[s[i]], i -sigh);
sigh = max(hash[s[i]], sigh);
}
//manlen记录长度最大值
maxlen = maxlen >= len?maxlen:len;
//记录每一个元素最新出现时的下标
hash[s[i]] = i;
}
return maxlen;
}
};