一.题目描述
3. 无重复字符的最长子串 - 力扣(LeetCode)
二.题目解析
题目还是很好理解的,就是找到一个最长的子串,但是要求子串中没有重复字符且子串必须是连续的。
三.算法原理
1.暴力解法
暴力解法其实就是枚举出所有可能的子串,然后判断子串是否有重复的字符,如果没有则记录下它的长度。遍历完所有子串,返回最长的长度即可。
方法就是固定第一个字符,然后依次增加后面字符的长度即可。
判断一个字符是否重复出现,我们只需要把每一个字符放到一个哈希表中即可,如果一个字符出现的次数>1,说明就重复出现了。
重复出现之后,就要更换第一个字符,然后right指针回到第一个字符,重新开始遍历。
时间复杂度:从整体上看,需要两个指针,left和right,left依次遍历字符串,right在固定好left的前提下也要遍历一边字符串,所以时间复杂度为O(N^2)。
2.滑动窗口
滑动窗口其实就是在暴力解法的基础上进行优化,最终实现的。下面我们对暴力枚举进行优化:
优化一:当我们在枚举子串时,如果发现最右边的字符已经出现一次了,那么我们right还有必要继续向右走么?
答案:是没有必要~~
因为这已经不符合题目的要求了。题目说的是没有重复字符的最长子串,你已经有了重复字符,你right就算走到结尾,这个子串也不可能是最终结果!
优化二: 既然出现了重复字符,right就没有必要继续往右走了,那么我们就移动left。
我们看下图,当left移动之后,子串中就没有重复字符了,此时我们更新len。
但是还有一个问题,left只移动一位就可以了么?
当然不是了,只是这个例子比较特殊,我们看下面这个例子:
重复字符有可能不是左端点,这时就算你left++,新的子串中依旧有重复字符,这个子串也不可能是答案。
接着,left移动到一个没有重复字符的位置之后,那么right还有必要回到left的位置重新开始遍历子串么?
没有~~
因为当left移动到这个位置之后,left~right这个区间里已经没有重复字符了,right从left重新走一遍没有任何意义!所以不需要回退right。
所以我们可以得出规律,当出现重复字符后,left应该一直移动直到left~right这个区间内没有重复字符。 当left移动后满足题目要求,我们就更新len。然后此时就继续让right遍历字符串。然后重复这个过程。
总结:在这个过程中,left和right都是向同一个方向移动,没有回退的过程,所以这就可以滑动窗口来解决问题。
时间复杂度:虽然代码是两层循环,但是right只遍历了一边数组,最坏情况下left也会遍历一边数组,所以时间复杂度为2n,整体上就是O(N).
四,代码实现
注意:我们这里既可以使用unordered_map来记录出现次数,也可以开一个大小为128的数组,来模拟哈希表。
int left = 0;
int right = 0;
unordered_map<char,int> dict;
int len = 0;
while (right < s.size())
{//进窗口dict[s[right]]++;//出窗口while (dict[s[right]] > 1){dict[s[left]]--;left++;}len = max(len, right - left + 1);right++;
}
return len;