bool rep(string str) {
unordered_set<char> seen;
for (char c : str) {
if (seen.find(c) != seen.end()) {
return true;
}
seen.insert(c);
}
return false;
}
int lengthOfLongestSubstring(string s) {
int l=0,r=1;
int cnt=0;
string str;
str=s[0];
while(r<=s.size()){
if(rep(str)){
l++;
}else{
r++;
cnt=max(static_cast<int>(str.size()),cnt);
}
str=s.substr(l,r-l);
}
return cnt;
}
↑第一个版本,写了一个很傻的
思路是:如果检测到双指针中间夹着的字符串含有重复字符,就将左指针递增;反之则将右指针递增。想了想怎么实现检查重复字符的操作,于是写出了rep函数。
好像有滑动窗口的思想,但是没啥用!如果双重循环暴力截取每一个子串(n^2),并且判断子串是否重复(n),最暴力的写法复杂度是O(n^3)。我除了把双重循环改成单层循环了,但是这样只变成了O(n^2),只能处理几万大小的数据,还是太慢了。
主要是rep那个函数傻,每次都要遍历一遍set。
版本2:
int lengthOfLongestSubstring(string s) {
unordered_set<char> seen;
int l = 0, r = 0;
int max_len = 0;
while (r < s.size()) {
if (seen.find(s[r]) == seen.end()) {//没找到,这个字符不是使用过的
seen.insert(s[r]);
max_len = max(max_len, r - l + 1);
r++;
} else {
seen.erase(s[l]);
l++;
}
}
return max_len;
}
不必在每次更新子串后,都从头判断当前子串是不是重复的,因为如果之前都不重复,那新加一个字符的话,只要直接查一下那个字符在不在就行了。版本1我做了好多无效的计算!
相比起1的最大遍历n次判断是否重复,2可以一次查询出该字符串是否为重复子串。把查询操作复杂度降到常数级别,现在的复杂度是O(n)了。
但是还有可以改进的地方:左指针不必每次都递增1,直接跳到重复字符的后面一个就可以。
比如abcb,需要右移两次才能变成cb,中间多判断了一次bcb是否为重复子串,这个也是不必要的。如果我们知道第一个b字符的位置,就可以直接把ab全都删掉。
为了在记录是否重复的情况下记录字符位置,我们可以把unordered_set换成unordered_map。这两者的作用差不多,但是map可以储存key和value,相比起只能储存key的set,正好满足我们的要求。
版本3:
int lengthOfLongestSubstring(string s) {
unordered_map<char,int> seen;
int l = 0, r = 0;
int max_len = 0;
for (int r = 0; r < s.size(); ++r) {
if (seen.find(s[r]) != seen.end()) {
l = max(l, seen[s[r]] + 1);
}
seen[s[r]] = r; // 更新当前字符的最新位置
max_len = max(max_len, r - l + 1); // 更新最长无重复子串的长度
}
return max_len;
}
l = max(l, char_index[s[r]] + 1),可以保证重复字符一定在框里。
比如abcbda,检测到abcb的时候,会把ab删去变成cb;继续检测到cbda时,能查到a的确已经出现过,但是出现的那段已经被删掉了。
等同于if(当前字符出现过){if(当前字符的下标在当前左指针前)}