题意:给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
最简单的想法:直接遍历整个字符串,判断当前字符是否出现过。没有出现,最长子串长+1;出现过,向前找到该字符,下一次从该字符的后一个位置开始遍历,重置长度为1.
时间复杂度为O(n),空间复杂度O(1);
但是如果字符串S由很长的无重复字串循环组成,如:”abcabcabc“。此时每次都要向前回溯最长子串长,导致算法的效率很低。因此,可以对这一回溯过程进行优化。即用空间换时间,多记录每个字符第一次出现的位置,避免用循环的方法回溯,而是直接找到上一次该字符出现的位置进行下一次遍历。有点类似于KMP。
不妨设当前的最长无重复字串的左端点对应字符为”L“,第一次出现的位置为”Pl“;右端点对应字符为”R“,第一次出现的位置为”Pr“。
从字符串首开始遍历,对于当前遍历到的字符S[i],判断是否重复出现过:
若第一次出现,记录下该字符曾经出现过,以及出现的位置,最长子串长度+1;
若原来出现过,将左端点L移动到该字符第一次出现位置的后一位(此处设为P+1,即第一次出现的位置为P),右端点移动到当前位置 i,并把从原来的L开始,到P这段子串中,所有出现过的字符的出现次数全部标记为0,更新目前的最长字串(目前找到的,不一定是结果)长度为i-p.
如此遍历整个字符串,就可以得到不含重复字符的最长字串长。
int lengthOfLongestSubstring(char * s){
int t=0,ans=0,maxn=0;
int num[260]={},pos[260]={};//num用于记录字符,pos记录对应字符第一次出现的位置
while(s[t]!='\0')
t++;
if(t==0)return 0;
for(int i=0;i<t;i++)
{
num[s[i]]++;
if(num[s[i]]>1) //如果某字符重复出现了
{
if(ans>maxn)maxn=ans;//更新最长子串
int temp=i-ans; //重复字符前有多少个字符,比如“abcb”,需要将a的出现次数重置为0
for(int j=pos[s[i]]-1;j>=temp;j--)
num[s[j]]=0,pos[s[j]]=0;
num[s[i]]=1;ans=i-pos[s[i]]-1; //更新当前的最长子串长
}
pos[s[i]]=i;ans++;//统一在循环时ans+1,因此上面的ans需要-1
}
if(ans>maxn)maxn=ans;
return maxn;
}