思路
lastRepeatIndex:表示上一次重复字符的下标
map<char, int> hash: 比如hash[‘a’] = 2,一方面该数据结构可以判断,当前遍历字符之前有没有出现过;另一方面,可以获取如果字符出现过,出现的位置在哪。
定义f数组,f[i] = k 表示str[0-i]序列的最长的不包含重复字符的子字符串的长度为k
动规转移方程:
| 1, i == 0
| f[i-1] + 1; i >= 1当前字符没有出现过
f[i] = |
| i - hash[str[i]],并更新lastRepeatIndex = i, hash[str[i]] = i; 当前字符出现过,并且在最近出现重复的字符之后
| i - lastRepeatIndex, hash[str[i]] = i; 当前字符出现过,并且在最近出现重复的字符之前
举个例子
"wobgrovw" 答案为6
index : 0 1 2 3 4 5 6 7
s[index] : w o b g r o v w
f[index] : 1 2 3 4 5 4 5 6
lastRepeatIndex :-1 -1 -1 -1 -1 1 1 1
hash[s[index]] : 0 1 2 3 4 5 6 7
起初,从index = 1开始:'o'字符没有重复,并且之前是没有重复字符的,所以f[1]=f[0]+1, lastRepeatIndex不变
后续直到index=5,'o'字符串出现时,此中间过程忽略
直到到'o'字符再次出现时,hash['o']为1,并且hash['o'] > lastRepeatIndex,说明出现重复的位置在上一次重复位置之后,需要调整 ,所以f[index]为index-hash['o'], 并更新hash['o']为当前字符的index,并更新lastRepeatIndex为hash['o']更新前的值.
最后直到'w'字符出现重复,因为hash['w'] < lastRepeatIndex,表示出现重复的位置在上一次重复位置之前,不会对当前结果产生影响, 所以f[index] = index-lastRepeatIndex,不更新lastRepeatIndex。
最后统计f数组中的最大值即可!
当然,也可以简化f数组,优化空间!
代码
int lengthOfLongestSubstring(string str) {
if(str.empty() || str.size() == 1) return str.size();
unordered_map<char, int> hash;
int preLen = 0, //上一次的长度
nowLen, // 本轮的长度
res = INT_MIN, //最终结果
lastRepeatIndex = -1; //上一次重复的下标
for(size_t index = 0; index < str.size(); ++index){
if(hash.count(str[index]) == 0 ){ //如果没有重复,就在上一次的基础上加1
nowLen = preLen + 1;
}else{ //有重复时,分情况
if(lastRepeatIndex > hash[str[index]]){ //如果当前重复的字符,在上一次重复字符的前面,那么这一次重复没有作用,完全可以使用nowLen = preLen + 1代替
nowLen = index - lastRepeatIndex;
}else{ 如果当前重复的字符,在上一次重复字符的后面,那么说明要调整长度,和上一次重复的下标值
nowLen = index - hash[str[index]];
lastRepeatIndex = hash[str[index]];
}
}
hash[str[index]] = index;
preLen = nowLen;
res = res > nowLen ? res : nowLen;
}
return res;
}