题目
给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
示例 1:
输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:输入: “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:输入: “pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是子串的长度,“pwke” 是一个子序列,不是子串。
失败的代码
bool find(string s,char obj)
{
for(int i=0;i<s.size();i++)
if(s[i]==obj) return true;
return false;
}
int cross(string s,int mid)
{
int ans1=0,ans2=0;
string temp;
for(int i=mid-1;i>=0;i--)
{
if(find(temp,s[i]))
{
break;
}
temp+=s[i];
ans1++;
}
for(int i=mid;i<s.size();i++)
{
if(find(temp,s[i]))
{
break;
}
temp+=s[i];
ans1++;
}
string temp2;
for(int i=mid;i<s.size();i++)
{
if(find(temp2,s[i]))
{
break;
}
temp2+=s[i];
ans2++;
}
for(int i=mid-1;i>=0;i--)
{
if(find(temp2,s[i]))
{
break;
}
temp2+=s[i];
ans2++;
}
return (ans1>ans2)?ans1:ans2;
}
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if(s.size()==0) return 0;
if(s.size()==1) return 1;
int max;
int mid=s.size()/2;
string before(begin(s),begin(s)+mid);
string after(begin(s)+mid,begin(s)+s.size());
int i1=lengthOfLongestSubstring(before);
int i2=lengthOfLongestSubstring(after);
int i3=cross(s,mid);
if(i1>i2) max=i1;
else max=i2;
if(i3>max) max=i3;
return max;
}
};
思路为分治策略,参考《算法导论》p38最大子数字问题,但是本题有些出入,该解法仅简单地分为向左向右两种情况,对于其余情况疏于考虑。
而对于最长字串经过中间的情况,讨论过多,对于该子问题没有想到在时间复杂度O(n)的情况下的解决办法,故宣告失败。
错误情况
输入: “bziuwnklhqzrxnb”
输出 10
预期结果 11
暴力解法
bool find(int start,int end,string s,char obj)
{
for(int i=start;i<end;i++)
{
if(s[i]==obj) return true;
}
return false;
}//区间 左开右闭
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int i=0,j=1,max=0,temp=0;
if(s.size()==0) return max;
if(s.size()==1) return 1;
for(;j<s.size();j++)
{
if(find(i,j,s,s[j]))
{
max=j;
break;
}
max=j+1;
}//i=0拉出来单独考虑
for(i=1;i<s.size();i++)
{
if(s[i-1]==s[j])
{
for(j+=1;j<s.size();j++)
{
if(find(i,j,s,s[j]))
{
//j++;
break;
}
temp=j-i+1;
}
}
if(temp>max) max=temp;
}
return max;
}
};
简单想了下,这样子时间复杂度好像也是O(
n
2
n^2
n2),但是用时还是爆炸了,我果然还是菜啊,可能又是哪里罗嗦了吧。
题解1:滑动窗口
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
//s[start,end) 前面包含 后面不包含
int start(0), end(0), length(0), result(0);
int sSize = int(s.size());
while (end < sSize)
{
char tmpChar = s[end];
for (int index = start; index < end; index++)
{
if (tmpChar == s[index])
{
start = index + 1;
length = end - start;
break;
}
}
end++;
length++;
result = max(result, length);
}
return result;
}
};
对比该解法,上面的暴力解法问题出在找了两遍重复元素,find(i,j,s,s[j])与if(s[i-1]==s[j])重复计算,浪费资源。
详细过程见https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-cshi-xian-/
题解2:哈希表优化
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
//s[start,end) 前面包含 后面不包含
int start(0), end(0), length(0), result(0);
int sSize = int(s.size());
unordered_map<char, int> hash;
while (end < sSize)
{
char tmpChar = s[end];
//仅当s[start,end) 中存在s[end]时更新start
if (hash.find(tmpChar) != hash.end() && hash[tmpChar] >= start)
{
start = hash[tmpChar] + 1;
length = end - start;
}
hash[tmpChar] = end;
end++;
length++;
result = max(result, length);
}
return result;
}
};
题解3:模拟替代哈希表
版本1
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
//s[start,end) 前面包含 后面不包含
int start(0), end(0), length(0), result(0);
int sSize = int(s.size());
vector<int> vec(128, -1);
while (end < sSize)
{
char tmpChar = s[end];
//仅当s[start,end) 中存在s[end]时更新start
if (vec[int(tmpChar)] >= start)
{
start = vec[int(tmpChar)] + 1;
length = end - start;
}
vec[int(tmpChar)] = end;
end++;
length++;
result = max(result, length);
}
return result;
}
};
版本2
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len = s.length();
// 对于字符串长为0时特殊处理(这里需要这么做,是因为最后答案输出时是ans + 1
if(len == 0) return 0;
//头尾下标指针初始化
int st = 0, en = 0;
int ans = 0;
bool ch[128];
// ch数组按照ASCII码的值记录每个字符是否已被使用
memset(ch, false, sizeof(ch));
// 记录第一位的字符
ch[s[0]] = true;
// 注意,因为String存储范围的下标其实是 0..(len-1),因此全部处理成en + 1 < len
while(en + 1 < len)
{
// 利用贪心,如果下一位超出String范围,或是已被使用过,则退出循环
while(en + 1 < len && !ch[s[en + 1]])
{
en ++;
ch[s[en]] = true;
}
// 由于ans存储的是尾指针-头指针的数值,因此比实际长度小1
ans = max(ans, en - st);
// 头指针后移一位
ch[s[st]] = false;
st ++;
}
// 这里使用ans + 1输出是为了将len = 1的情况一起包括进来,感兴趣的码友可以尝试一下此类数据
return ans + 1;
}
};
两种方法比较类似,都是采取索引来优化算法。