无重复字符的最长子串原题地址
方法一:滑动窗口(双指针) + 哈希集合
考虑找出字符串s的所有的无重复字符的子串,求出这些子串长度的最大值即可。
使用下标 [left,right] 来维护子串。我们只需要找到每一个 left 对应的所有 right 即可,换句话说,固定了 left 之后,我们让 right 从 left 的位置开始,向后滑动,直到 [left,right] 出现重复字符为止。
以下为所有无重复字符的子串:
(a) b c a b c b b
(a b) c a b c b b
(a b c) a b c b b
a (b) c a b c b b
a (b c) a b c b b
a (b c a) b c b b
a b (c) a b c b b
a b (c a) b c b b
a b (c a b) c b b
a b c (a) b c b b
a b c (a b) c b b
a b c (a b c) b b
a b c a (b) c b b
a b c a (b c) b b
a b c a b (c) b b
a b c a b (c b) b
a b c a b c (b) b
a b c a b c b (b)
由于我们要找的是这些子串中最长的那个,所以固定 left 后,先让 right 右移至出现重复字符或者 right 移动到字符串的末尾,再统计该子串的长度,即 right-left+1 ,求出其中的最大值即可。
需要统计如下子串的长度:
(a b c) a b c b b
a (b c a) b c b b
a b (c a b) c b b
a b c (a b c) b b
a b c a (b c) b b
a b c a b (c b) b
a b c a b c (b) b
a b c a b c b (b)
显然,当某条子串 [left,right] 的右端点 right 已经到达字符串的末尾时,若 left 继续右移,只会缩短子串长度,所以此时无需继续统计。
为了判断子串是否已经出现重复字符,可以利用哈希集合(如 C++ 中的 unordered_set<char> ),来存储 [left,right] 中的所有字符,当 right 右移时,就把最右边的字符添加到哈希集合中;当 left 右移时,就把最左边的字符从哈希集合中删除。这样,每次只需判断子串新增加的字符是否已经在哈希集合中出现过即可。
// 方法一:滑动窗口
class Solution
{
public:int lengthOfLongestSubstring(string s){if (s.empty()){return 0;}unordered_set<char> us;int ans = 0;// [left,right] 表示子串int left = 0, right = 0;us.insert(s[0]);while (1){// 右指针右移,直到出现重复字符while (right + 1 < s.size() && !us.count(s[right + 1])){us.insert(s[++right]);}// 子串长度为 right-left+1ans = max(ans, right - left + 1);// 右指针走到尾,子串不可能更长if (right == s.size() - 1){break;}// 左指针右移,在哈希表中去掉最左边的字符us.erase(s[left++]);}return ans;}
};
方法二:对方法一的优化,实现左指针 left 的快速跳转
方法一是固定 left ,让 right 尽可能右移,从而统计所有的 [left,right] 区间长度 right-left+1 的最大值,这样最坏情况下 left 会遍历完整个字符串。
考虑用 key-value 模型的哈希表(如 C++ 中的 unordered_set<char, int> ),每次把 [0,right] 的字符及其对应下标都存进去。
我们用 right 遍历字符串,若 right 右移后指向的字符在哈希表中出现过,且最后出现的下标在 [left,right] 的范围内,说明此时的子串 [left,right] 已经出现了重复字符,此时 left 应该跳转到 s[right] 最后一次出现的位置的后面。
下面的例子中, right 本来指向 f ,右移后指向 c , left 就要从 a 直接跳转到第一个 c 后面,否则 [left,right] 会出现重复字符。
(a b c d e f) c
a b c (d e f c)
每次 right 右移后,都要把 s[right] 及对应的下标 right 都存储到哈希表中,也就是把 s[right] 当前最后出现的位置存储到哈希表中。
// 方法二:滑动窗口优化
class Solution
{
public:int lengthOfLongestSubstring(string s){if (s.size() <= 1){return s.size();}unordered_map<char, int> um;int ans = 0;// [left,right] 表示子串int left = 0, right = 0;um[s[0]] = 0;// 右指针右移,直到出现重复字符while (++right < s.size()){auto it = um.find(s[right]);// 出现重复字符if (it != um.end()){// 左指针跳转到重复字符上一次出现的位置之后left = max(left, it->second + 1);}// 子串长度为 right-left+1ans = max(ans, right - left + 1);um[s[right]] = right;}return ans;}
};