题目
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 **子串** 的长度,"pwke" 是一个子序列,不是子串。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
分析:
子串的要求是,从原字符串能直接摘出来的,要连续。
暴力解法
暴力解题的思路是,将所有的无重复子串都列出来,返回长度最大的那个子串的长度。但是这种解法会超时,时间复杂度为。
滑动窗口
对第一个例子"abcabcbb" 进行分析。我们首先按顺序遍历字符串,如“a”, “ab”,“abc”.此时的字符串“abc”是目前所得的最大子串。
接着往后遍历,“abca”,这时,子字符串中有重复字符,因此我们将这个子字符串中第一次出现的重复字符“a”,去掉。用一个滑动窗口来模拟这个行为,滑动窗口刚开始是把“abc”都框起来的,到了“abca”的时候,为了符合题意,我们在去掉第一次出现的重复字符“a”时,就相当于把滑动窗口的起始位置放到了第一次出现的重复字符的下一个位置。此时记录max_length=3,即“abc”的长度。
现在滑动窗口里是“bca”,接着往后遍历,得到“bcab”,同上,我们再次滑动我们的窗口,将第一次出现的重复字符“b”,去掉。得到新的子串为“cab”,此时记录max_length=3,即“cab”的长度。把滑动窗口的起始位置放到了第一次出现的重复字符的下一个位置。即滑动窗口的起始位置现在是“c”了。
以此类推。 那么最长的无重复子串的长度其实就是滑动窗口的长度。滑动窗口的长度由它的起始位置决定。它的开始位置好说,那么我们只需要找到每个字符的最后出现位置。
为了求出滑动块窗口的长度size, 我们用left指针指向它的左边界,如果遍历到的字符没有出现过,就扩大其右边界。使用hashmap建立字符与其最后出现位置的映射。如果出现过,分两种情况:
1. 当前字符出现在滑动窗口内,则left指向窗口内字符出现位置的下一个位置,扩大右边界,hashmap中更新该字符最后出现的位置;
2. 当前字符没有出现在滑动窗口内,当前字符在hashmap中存储过;则我们可直接将这个字符加到滑动窗口内,扩大右边界,ashmap中更新该字符最后出现的位置。
维护size长度,与每次滑动窗口实际大小做比较,取大。
将所有位置初始化为-1,使用256大小的整形数组来代替hashmap。
C++代码如下
class Solution{
public:
int lengthOfLongestSubstring(string s)
{
vector<int>m(256, -1); //初始化所有字符的位置为-1
int left = -1; //滑动窗口的左边界指针
int size = 0; //记录无重复子串的当前最大长度
int len = s.size(); //字符串的长度
for(int i =0; i<len; i++) //遍历字符串
{
left = max(left, m[s[i]]); //滑动窗口的左边界为当前字符上一次最后出现的位置的下一个位置
m[s[i]] = i; //更新当前字符出现的最后位置
size = max(size, i - left); //返回已记录的最大长度与滑动窗口当前长度的最大值
}
return size;
}
}