LeetCode 3_Longest Substring Without Repeating Characters

这今天正好有时间,所以多刷几道吧。这是LeetCode第三题,虽然难度为中等,却花费了我近三个小时的时间。

原因主要是对题目理解多次出现了偏差,看来英语水平还有待提高啊,虽然过了六级,但离真正使用还是有一定差距的。废话不多说了,开始说题目

原题:

Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for "abcabcbb" is "abc", which the length is 3. For "bbbbb" the longest substring is "b", with the length of 1.

这里着重说一下题意:给定一个字符串,找出其所有没有重复的字母的子串的最大长度。也就是说找出所有子串,把其中有重复字母的全部去掉,看剩下的所有子串中最大长度的子串是多长。这里举得例子也有点儿问题,容易让人误解。当然最关键还是过于相信自己的英语水平了,没有仔细审题。

思路分析:

最简单的思路就是求出以每个字母开头的没有重复字母的子串长度,选择最长的一个。这样只要写一个求从指定开始到第一个重复字母出现位置长度的函数,然后依次改变指定位置将字符串传入即可。但显然这种操作是属于确定一个历遍所有,在确定一个再历遍所有的算法。在第一题中已经说明,这种算法是比较笨的,不建议采用。但为了学习,仍将代码给出:

<span style="font-size:14px;"><span style="font-size:18px;">class Solution {
public:
		int	lengthOfSubString(string& s,int pos)
		{
			string::size_type Length=0;
			string::size_type head=pos;
			string::size_type tail=pos;
			while(tail!=s.size())//历遍s
			{
				string::size_type p = s.find_first_of(s[tail],head);
				if(p!=tail)//有相同的了
				{
					Length=tail-head;
					break;
				}
				++tail;
			}
			if(tail-head>Length)
				Length=tail-head;
			return Length;
		}
   int lengthOfLongestSubstring(string s)
	{
		int maxLength=0;
		for(string::size_type i=0;i<s.size();++i)
		{
			if(lengthOfSubString(s,i)>maxLength)
			{
				maxLength=lengthOfSubString(s,i);
			}
		}
		return maxLength;
    }
	
};</span></span>

程序运行结果为96ms,在C++代码中算是最靠后的了,原因前面已经说明。说句题外话,虽然代码用类似穷举法的“笨方法”。但从结果可以看出96ms,在所有实现中仍然是比较靠前的,比C#和Java中最快的仍快很多,这也说明了C++语言本身的运行速度是很快的。当然,C的就更快了,在速度和设计难度中间找到一个平衡点是我们应该考虑的问题。

下面考虑算法改进,这个思路稍微有点儿复杂,花费了不少时间,关键是历遍方法的选择,以及出现重复字母之后的指针操作。首先肯定要有一个总的历遍指针,从头到尾扫描整个字符串,然后要有一个指针指向没有重复的字符串的首地址,最后还要有一个指针用于寻找搜索结果。这么说大家可能更迷糊了,还是直接上代码吧。

改进后的代码:

<span style="font-size:14px;"><span style="font-size:18px;">class Solution {
public:
		int	lengthOfLongestSubstring(string &s)
		{
			string::size_type maxLength=0;
			string::size_type head=0;
			string::size_type tail=0;
			while(tail!=s.size())
			{
				string::size_type pos = s.find_first_of(s[tail],head);
				if(pos!=tail)//有相同的了
				{
					if(tail-head>maxLength)
						maxLength=tail-head;
					head=pos+1;
				}
				++tail;
			}
			if(tail-head>maxLength)
				maxLength=tail-head;
		return maxLength;
		}
		
};</span></span>
具体程序怎么执行就不多说了,有兴趣的可以自己读一下,这对新手也是一个进步过程。改进后程序运行速度明显提升,达到了16ms。算是比较好的一个结果了。为了进一步提高,再来一段大神的代码

大神代码:

<span style="font-size:14px;"><span style="font-size:18px;">class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int m[256];
        fill(m, m+256, -1);
        int maxLen = 0, lastRepeatPos = -1;
        for(int i=0; i<s.size(); i++) {
            if (m[s[i]]!=-1 && lastRepeatPos < m[s[i]]) lastRepeatPos = m[s[i]];
            if ( i - lastRepeatPos > maxLen ) maxLen = i - lastRepeatPos;
            m[s[i]] = i;
        }
        return maxLen;
    }
};</span></span>

 
 

大神的代码运行时间也是16ms,但简化了很多,编程思路也不相同。这种表映射方法应该是一种固定的解法吧,本人对着方面不是很了解,希望有明白人指点。这种编码的主要思路是将字符串映射到一个表中。具体映射方法如下:

这里构造一张表flag(程序中为m表),正如程序所设定的这个表共有256列,这是因为ASCII中可显示字符共有256个。但这里为简单起见,我们假设表中只含小写的英文字母,之所以在起始时填充-1是因为没有字符对应的ASCII码-1,表如下:

然后假设字符串s="afbfxyz"。然后将每个字符的位置(下标)映射到表中对应的位置,即a的下标为0,则表中a下面的字段填0,f下填1...如图:

这个填表过程由下面语句实现
<span style="font-size:14px;"><span style="font-size:18px;"><span style="white-space:pre">	</span>m[s[i]] = i;</span></span>

在这个过程中会不断更新maxLen

<span style="font-size:14px;"><span style="font-size:18px;"> <span style="white-space:pre">	</span>if ( i - lastRepeatPos > maxLen ) maxLen = i - lastRepeatPos;</span></span>

这对应字符没有重复的情况。当出现重复字符时,例如对应s="abcbad"这种情况:当出现第二个b时,由于第一个b应经将b下面的字段改为1了,所以m[s[i]]!=-1,而LastReaptPos还等于-1,显然第一个b是在LastReaptPos之后的(lastRepeatPos < m[s[i]]),所以移动LastReaptPos,将其移动到第一个b的位置。而当第二个a出现时,由于LastReaptPos已经移动到第一个b的位置了,所以不满足lastRepeatPos < m[s[i]],也就不移动LastReaptPos了。

总结:

最简单直接的方法容易理解,但算法效率比较低。而改进后的基本达到了应该达到的效果,理解起来也不太难。最后大神的思路对没有接触过这种算法的人可能比较难,但其扩展性是比较好的。可以看作是处理字符串问题的一个通用方法。大家有精力还是仔细研究一下为好。


你可能感兴趣的:(LeetCode,算法,时间复杂度,String)