题目描述:
给定一个字符串,找出不含有重复字符的最长子串的长度。
示例:
给定"abcabcbb"
,没有重复字符的最长子串是"abc"
,那么长度就是3。
给定"bbbbb"
,最长的子串就是"b"
,长度是1。
给定"pwwkew"
,最长子串是"wke"
,长度是3。
请注意答案必须是一个子串,”pwke” 是 子序列 而不是子串。
思路:
这里以"abcabcbb"
作为例子:
思路1:
最简单能想到的,就是遍历这个字符串的所有子串了,要按顺序写出来比较不会乱:ab, abc, abca, abcab , abcabc, ……, abcabcbb, bc, bca, ……
(注意这里我从长度为2的子串开始判断,max_len
初始化为1,只要输入字符串不为”“,那么子串长度至少为1)
存在的问题:
时间复杂度为 O(n3),遍历所有子串是两层循环,然后判断是否重复又是一层,当然判断重复这个可以改进,后续再说。
而且这样做有一个问题,就是会增加冗余的判断:比如abca
已经判断是含有重复字符的子串了,那么其实没必要再去对后续的包含该重复子串abca
的子串如:abcab
等进行判断。
思路2:
如下图所示,当我们遍历到的子串含有重复字符的时候,可以跳出内层循环,进入下一个起始的下标:
代码如下:
int lengthOfLongestSubstring(string s) {
if (s == "")
return 0;
int s_sz = s.size(); // 字符串长度
int max_len = 1; // 最小值为1,空串为0
for (int i = 0; i < s_sz; i++){
for (int j = i + 1; j < s_sz; j++){
string tmp_s = s.substr(i, j-i+1);
bool dup = checkDuplicate(tmp_s); // 检查该子串是否有重复的字符
if (dup){ // 如果有重复,那就进入外层循环的下一个
break;
}
else{
int tmp_s_len = tmp_s.size();
max_len = tmp_s_len > max_len ? tmp_s_len : max_len;
}
}
}
return max_len;
}
存在的问题1:
这样做是能减少很多时间,但是由于检查子串含有重复字符的函数checkDuplicate
的时间复杂度为 O(n),我一开始是从头到尾遍历子串,通过map数据结构来判断的,如果该字符在map中未保存,则保存下来,如果已经有保存了,说明有重复,则直接返回true
表示该子串有重复字符。
这个函数的复杂度为O(n2),这样一来提交sol的时候会超时:
bool checkDuplicate(string s){ // 检查子串是否有重复
map<char, int> m;
int s_sz = s.size();
for (int i = 0; i < s_sz; i++){
if (m.count(s[i]) == 0){
m[s[i]] = 1;
}
else{
return true; // 有重复
}
}
return false; // 没有重复
}
改进:
用空间换时间的思路,创建一个hash table,初始化都为0,然后如果该字符未在哈希表中出现则置为1,如果该字符已经出现则也是返回true
。这里我理解的是每个字符都有对应的ascii码:
bool checkDuplicate(string s){ // 检查子串是否有重复
map<char, int> m;
int s_sz = s.size();
vector<int> hash_table(256, 0);
for (int i = 0; i < s_sz; i++){
if (hash_table[s[i]] == 0){
hash_table[s[i]] = 1;
}
else{
return true; // 有重复
}
}
return false; // 没有重复
}
存在的问题2:
改完上面的问题,还是会超时,这时我就想,会不会是寻找子串的过程还是有多余的地方。想了想果然是!
比如,对于输入字符串abcabcbh
, 当我max_len
为3的时候,那么其实我并不需要去查看 子串起始字符下标为 5,6的子串情况的,因为就算字符起始下标为5,那么cbh
这个子串长度为3,即就算这个子串不含重复字符,最好的情况也就只是3:
改进:
在子串起始的遍历的循环中,加入一个判断max_len
是否 ≥ s_sz - i
的判断,如果是则直接退出循环返回’max_len’即可
for (int i = 0; i < s_sz; i++){
if (max_len >= (s_sz - i))
break;
for (int j = i + 1; j < s_sz; j++){
string tmp_s = s.substr(i, j-i+1);
bool dup = checkDuplicate(tmp_s);
……
代码:
bool checkDuplicate(string s){ // 检查子串是否有重复
map<char, int> m;
int s_sz = s.size();
vector<int> hash_table(256, 0);
for (int i = 0; i < s_sz; i++){
if (hash_table[s[i]] == 0){
hash_table[s[i]] = 1;
}
else{
return true; // 有重复
}
}
return false; // 没有重复
}
int lengthOfLongestSubstring(string s) {
if (s == "")
return 0;
int s_sz = s.size();
int max_len = 1; // 最小值为1,空串为0
for (int i = 0; i < s_sz; i++){
if (max_len >= (s_sz - i))
break;
for (int j = i + 1; j < s_sz; j++){
string tmp_s = s.substr(i, j-i+1);
bool dup = checkDuplicate(tmp_s);
if (dup){ // 如果有重复,那就进入外层循环的下一个
break;
}
else{
int tmp_s_len = tmp_s.size();
max_len = tmp_s_len > max_len ? tmp_s_len : max_len;
}
}
}
return max_len;
}