LeetCode刷题(3)——Longest Substring Without Repeating Characters

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写的,有两种,即我的那种方法和上面写的另一个人的方法。


.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值