1、题目:
Given a string, find the length of the longest substring without repeating characters.
Examples:
Given "abcabcbb"
, the answer is "abc"
, which the length is 3.
Given "bbbbb"
, the answer is "b"
, with the length of 1.
Given "pwwkew"
, the answer is "wke"
, with the length of 3. Note that the answer must be a substring, "pwke"
is a subsequence and not a substring.
代码框架:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
}
};
2、我的解答:
class Solution {
public:
bool NoRepeatString(string s) {
for(int i=0;i<s.length()-1;i++)
for (int j = i+1; j < s.length(); j++)
{
if (s[i] == s[j])
return false;
}
return true;
}
int lengthOfLongestSubstring(string s) {
if(s=="")
return 0;
for (int j = s.length(); j > 0; j--)
for (int i = 0; i < s.length()-j+1; i++)
{
if (NoRepeatString(s.substr(i, j)))
return j;
}
}
};
这段解答本来以为是对的,但不可思议的是,居然在LeetCode上错误了,而错误的原因是,他们用了一个非常长的字符串进行测试,报错如下:
后来发现是我计算机的问题,如果在其他的电脑上运行就不一定会出错,而在LeetCode上出错的原因是超时。我用到了四层的循环,时间复杂度是O(n4)。
通过查看solution,我知道了我的这个解法属于BF算法(brute force)。BF算法,即暴风(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
3、我的解答第二版:
我又想到了一种方法,建立一个字符串ms,原字符串为s,从s的第一个字符往后读,如果遇到的字符是ms中没有的,就将其添加进ms,如果ms中有的,则将ms中的相同的字符剔除,保证ms中没有重复的字符。这样的话属于动态的添加和删除,ms的最后长度就是最长无重复子字符串的长度了。
在C++的string类中,有几个好用的方法:
(1)string.append()
比如string s="123",s1="4567895",char s3='7';
那么s.append(1,s3)表示在s的末尾插入一个s3字符;s="1237"
s.append(s1,2,3)表示在s的末尾插入“678”,从索引2以后(包括2)开始连续3个。
s.append(s1),表示在s的末尾插入s1。
(2)string.find()
比如string st1("babbabab");
cout << st1.find('a') << endl;//1 由原型知,若省略第2个参数,则默认从位置0(即第1个字符)起开始查找
cout << st1.find('a', 2) << endl;//4 在st1中,从位置2(b,包括位置2)开始,查找字符a,返回首次匹配的位置,若匹配失败,返回-1
cout << (st1.find('c', 0) == -1) << endl;//1
(3)string.substr()
截取子字符串。
比如string st1("babbabab");那么str1.substr(2,4)表示从索引2以后(包括2)开始截取4个,即输出为"bbab"
另外在<aigorithm>库中还有max和min函数,用于多个数比较大小的。
我的程序是:
// 错误方法,没有考虑到str="cdd"这种情况
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if (s == "")
return 0;
string ms = "";
ms.append(1, s[0]); //第一个参数是指定插入字符的数目
for (int i = 1; i < s.size(); i++)
{
if (ms.find(s[i]) != -1)
ms = ms.substr(ms.find(s[i]) + 1, ms.size() - ms.find(s[i]) - 1);
ms.append(1, s[i]);
}
return ms.size();
}
};
这种程序对于"cdd"这种字符串是不行的,因为第二个d进去之后,ms的长度只有1,还不如ms上一次的长度,因为我认为需要将每次发现重复字符的时候,操作之前先ms的长度保留下,作为最大长度,然后再发现新的重复字符,仍然如此,最后得到一个最大长度,用这个长度和ms的长度中的较大的一个作为答案。
4、我的解答第三版:
根据上面分析的思路,得到的可以通过的程序如下:
// 在上面错误的方法上进行改进,对每次找到重复字符后,将ms字符串的长度保留下来。
// 通过测试,我的答案,击败了24.81%,时间49ms,要改进的话只能用动态规划了。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if (s == "")
return 0;
string ms = "";
ms.append(1, s[0]); //第一个参数是指定插入字符的数目
int max_length = 0;
for (int i = 1; i < s.size(); i++)
{
int position = ms.find(s[i]);
if (position != -1)
{
if (ms.size() > max_length)
max_length = ms.size();
ms = ms.substr(position+1, ms.size() - position - 1);
}
ms.append(1, s[i]);
}
int m = ms.size();
return max(m, max_length);
}
};
5、别人的解答1:
击败58.01%,35ms。
分析:这种方法是首先建立一个256个字符的数组,每个字符都对应一个位置,则每个字符都唯一对应一个整数。然后给每个字符赋值,赋的是其索引值加1,表示从字符串开始到该字符所经过的长度,假设某个字符的上一次出现的时候赋值为ptr,该字符下一次出现时赋值为i,那么该字符在两次出现之间的字符串长度则为i-ptr。
一旦字符串中出现了重复字符,则字符串计数重新开始,即更新ptr的值,ptr代表了一个出现两次以上的重复字符最近一次出现的时候赋值(上一次出现)。所以这里的和我的方法中的ms字符串长度思想一致。
每次重新计数之前均要将上次的最大长度保留在max_length中。故在max_length = max(max_length, i-ptr+1)表示max_length 是取之前的最大长度和ms字符串长度的较大值。
if(prevs[i]) > ptr表示碰到了一个字符,该字符以前至少出现过一次,且上次出现时候的赋值要高于之前的那个重复字符。(比如对于abcabc,碰到第二个b时候,prev['b']=2,大于ptr=1)
// 击败58.01%,用时35ms
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if (s == "")
return 0;
int prev[256] = { 0 }; //建立一个数组,有256个元素,均置0
int max_length = 0, ptr = 0;
for (int i = 0; i < s.size(); i++)
{
if (prev[s[i]] > ptr)
{
ptr = prev[s[i]];
}
prev[s[i]] = i + 1;
max_length = max(max_length, i-ptr+1);
}
return max_length;
}
};
6、标准答案:
分析:如果一个子字符串sij从索引i到j-1都已经被确认没有重复字符,那么我们只需要判断s[j]是否存在于sij中。如果我们使用一般的搜索方法,我们的时间复杂度就是O(N^2)。但我们可以优化一下,通过将哈希表作为滑动窗口,使时间复杂度降到线性。
滑动窗口方法通常用于解决数组/字符串问题,这个窗口是数组/字符的部分元素组成的,通常用起始和终止索引来定义。比如[i,j)表示一个左封闭右开放的窗口,我们可以将这个窗口向右移动一格,则窗口变成了[i+1,j+1)。
回到我们的问题,我们使用哈希表存储当前窗口[i,j)中的元素(字符),这里的j初始化时等于i。然后我们将j向右滑动,如果元素没有出现在哈希表中,我们就将j向右增长,如果元素出现在哈希表中,我们在该点得到一个最大的子字符串长度,该长度为j-i+1,最终我们可以得到答案。
标准答案是用Java写的,有两种,即我的那种方法和上面写的另一个人的方法。
.