给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
0 <= s.length <= 5 * 104
s
由英文字母、数字、符号和空格组成
这道题最先想到的是哈希表与滑动窗口,当然你用两层循环肯定没有错,但是时间复杂度会很高。
看题我们知道s最大长度应该是95(参照anscii表,把不在上述范围内的给去掉)
我们可以设置一个长度为127的数组来存储字符的映射(参照anscii表的映射)
对于哈希表与滑动窗口,肯定需要用到双指针,left和right
那么这两个指针都做了什么工作呢?
right指针用来第一次便利,并且将扫描到的字符的映射数组的值改为1作为标记,直到扫描到映射数组值为1,那么当前已经扫描到了第一个无重复的子串了,等待left做接下来的工作。
left指针用来将right前边的所有映射清0,并记录新的窗口的起始点,left和right从新的起始点开始工作。
让我们来看一下这个代码
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//哈希表的映射加滑动窗口
if (s[0]=='\0')
return 0;
int mapping[127]={0};
int left = 0;
int right = 0;
int count = 0;
int max = 0;
int temp=0; //记录新的窗口起始点
while (s[right]!='\0'){
if(mapping[int(s[right])] == 0){ //把当前出现的字符对应的位置的数据增加
mapping[int(s[right])]=1;
count++;
right++;
}
else{
if(count>max){
max = count;
}
if (max>=95) //最大值是95,遇到95就退出
return 95;
if(left!=right){ //把前边的映射数组清空
if(s[left]==s[right])
temp = left+1; //找到新的滑动窗口起始点并记录
else mapping[int(s[left])]=0;
left++;
}
else {mapping[int(s[temp-1])]=0;left = right = temp;}
count =0;
}
}
if(count>max)
return count;
return max;
}
};
但是上述代码仍有优化的空间
“left指针用来将right前边的所有映射清0,并记录新的窗口的起始点,left和right从新的起始点开始工作。”
这个思路固然没错,但是,我们把right前,新的起始点后的映射数组清零再重新计数做了一遍重复的工作,所以我们可以让left把新的起始点之前的映射数组给清零,然后再让right继续前进。可以节省很多重复性的工作
class Solution {
public:
int res = 0;
int p[1000];
int lengthOfLongestSubstring(string s) {
p[s[0]] ++;
for(int i = 0,j = 0;i < s.size();)
{
while(p[s[j]] == 1 && j < s.size())
{
j ++;
p[s[j]] ++;
}
res = max(res,j - i);
p[s[i]] --;
i ++;
}
return res;
}
};
上述代码看似是两重循环,其实比第一个代码效率更高。