题目
给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
Hash法 时间O(n^2),空间O(n)
Hash法存储,例如dvdf字符串获取最长子串,步骤如下:
hash<char, int> 存储对应字符出现的index
循环遍历
1. i =0,查找d是否在hash,不存在则将d+index加入hash => hash<d, 0>
2. i =1,查找v是否在hash,不存在则将v+index加入hash => hash<d, 0><v, 1>
3. i =2,查找d是否在hash,存在将对应的<d, 0>的index取出,重新将hash更新数据index+1到i,此时hash<v, 1><d, 2>
4. i =3,查找f是否在hash,不存在则将f+index加入hash => hash<v, 1><d, 2><f, 3>
5. 输出hash长度
代码如下:
int lengthOfLongestSubstring(string s) {
if (s.empty())
{
return 0;
}
unordered_map<char, int> maxStrMap;
int maxLen = 0;
for (int i = 0; i < s.size(); ++i)
{
//无重复
if (maxStrMap.find(s[i]) == maxStrMap.end())
{
maxStrMap[s[i]] = i;
if (maxLen < maxStrMap.size())
{
maxLen = maxStrMap.size();
}
}
//有重复
else
{
//此时相当于遇到了重复,去除这个重复,重复点的下一个开始继续搞
//此时重复的元素的下标 留dvdf, i = 2时maxStrMap[s[i]]记录着0,也就是index=0
int index = maxStrMap[s[i]];
maxStrMap.clear();
for (int j = index + 1; j <= i; ++j)
{
maxStrMap[s[j]] = j;
}
}
}
return maxLen;
}
不过LeetCode运行之后。。。
运行运行结果太惨了,所以换了个方法时间复杂度O(2n)的滑动窗口以及不需要而外空间的O(n^2)滑动窗口方式
滑动窗口法 时间O(2n),空间O(n)
然鹅下面的方法LeetCode过不了。。。我本地使用测试用例没有任何问题,但是就是过不了。。。
核心思想类似于双指针,快指针先走,遇到相同字符慢指针走即可。
int lengthOfLongestSubstring1(string s) {
if (s.empty())
{
return 0;
}
int maxLen = 0;
int len = s.size();
unordered_map<char, int> uniCharMap;
int i = 0, j = 0;
while (i < len && j < len)
{
if (uniCharMap.find(s[j]) == uniCharMap.end())
{
uniCharMap[s[j]] = j++;
if (maxLen < j - i)
{
maxLen = j - i;
}
}
else
{
uniCharMap.erase(s[i++]);
}
}
return maxLen;
}
优化滑动窗口法 时间O(n),空间O(n)
滑动窗口法可能需要最多2n个步骤,事实上,它可以被进一步优化为仅需要 n 个步骤。我们可以定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。 当我们找到重复的字符时,我们可以立即跳过该窗口。hash存储了当前key,如果重复了,可以移动的下一个下标的位置。
代码如下:
int lengthOfLongestSubstring3(string s) {
if (s.empty())
{
return 0;
}
int maxLen = 0;
int len = s.size();
unordered_map<char, int> uniCharMap;
int i = 0;
for (int j = 0; j < len; ++j)
{
if (uniCharMap.find(s[j]) != uniCharMap.end())
{
i = std::max(uniCharMap[s[j]], i);
}
maxLen = std::max(maxLen, j - i + 1);
uniCharMap[s[j]] = j + 1; //记录当前key如果重复,可以跳到的下一个位置value
}
return maxLen;
}
但是LeetCode运行并不是很快,也可能是调用unordered_map会消耗一些时间,最后有一个时间复杂度为O(n^2),空间复杂度为O(1)的反而运行极快。
复杂版滑动窗口,时间O(n^2),空间O(1)
不借助而外空间的滑动窗口版本。代码比较简单,见下面。但是运行效率很高!!!
int lengthOfLongestSubstring2(string s) {
if (s.empty())
{
return 0;
}
int maxLen = 0;
int len = s.size();
int pre = 0;
for (int end = 0; end < len; ++end)
{
for (int i = pre; i < end; ++i)
{
//发现重复
if (s[end] == s[i])
{
pre = i + 1; //往后移动
break;
}
}
if (end - pre + 1 > maxLen)
{
maxLen = end - pre + 1;
}
}
return maxLen;
}