3. 无重复字符的最长子串
题目描述
给定一个字符串 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
由英文字母、数字、符号和空格组成
题解
思路:从第一个字符开始,不断扩大子串。如果出现重复,则从重复的字符处重新开始扩大子串。
滑动窗口
- 哈希表存储字符,用于判断是否有重复字符出现。
存在问题:算法执行时间非常长(大于600 ms)
可能原因:
- 哈希表被反复清空
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> umap;
int len = 0;
int max = 0;
for (int i = 0; i < s.length(); i++) {
if (umap.find(s[i]) == umap.end()) {
umap.insert({s[i], len++});
} else {
umap.clear();
max = max > len ? max : len;
i-=len; // 从失败的字符重新开始匹配。
len = 0;
}
}
max = max > len ? max : len; // 处理最长子串在结尾的情况
return max;
}
};
[!NOTE]
unordered_map
插入的顺序是{value,index}
。find
查找的时候,查找value
值。如果找到,则返回key
键;如果没有找到,则返回.end()
末尾。
滑动窗口优化
此解法参考Krahets。耗费时间10 ms,速度提升近60倍。因此,哈希表的清空.clear()
是一个非常耗费时间的过程。
核心思想:避免清空哈希表,保持哈希表内元素下标最新。
class Solution {
public:
int lengthOfLongestSubstringImproved(string s) {
unordered_map<char, int> umap;
int left = -1, len = 0, maxLen; // left:最长无重复字符子串的最右端。起始情况最长字符为空,所以left为-1
for (int right = 0; right < s.size(); right++) { // right:指向被判断的字符
if (umap.find(s[right]) != umap.end()) // 在哈希表中,被查找到
left = max(left, umap.find(s[right])->second); // 更新左指针。反例abba,足以解释为什么要用max
umap[s[right]] = right; // 哈希表记录。保持哈希表中的元素始终是不重复的,且坐标都是最新的,相当于窗口在滑动
maxLen = max(len, right - left); // 更新结果
}
return maxLen;
}
};
难理解点
left = max(left, umap.find(s[right])->second)
举例,输入字符串abba。
当right指向第二个b时,出现重复。此时,left = -1
,umap.find(s[right])->second) = 2
。left就直接移动到了第二个b的位置,即从重复字符开始重新匹配。注意,此时哈希表中的a的下标仍然是0。
当right指向第二个a时,出现重复。此时,left = 2
,umap.find(s[right])->second) = 0
,即重复字符不是left指向的字符,此时left就不想要移动。
umap[s[right]] = right
该方式:
- 如果键值对不存在,则插入
- 如果键值对存在,则修改值对应的键
maxLen = max(len, right - left)
求两个范围内的个数,一般情况下需要+1,即其中一个端点。
但是,理清left和right的含义。left:最长无重复字符子串的最右端;right:被判断的字符,即最长不重复子串的在一个,相当于已经+1了。