题目:
Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
翻译:
给定一个字符串,找出它的最大回文子串,返回。
分析:
所谓”回文“,是指倒着读和正着读结果都一样的字符串,例如”abcba"或“abba"。
这个题目有很多种解法,下面将依次进行分析。
1.Brute force (暴力破解法)
即依次得到给定字符串的每一个子串,判断这个子串是否为回文串,找到最长回文子串。
暴力破解法获取每个子串用了两个for循环(O(n^2)),在判断该子串是否为回文时又用了一个while循环(O(n)),所以时间复杂度为O(n^3)。
代码如下,这段代码在OJ上是没法通过的,因为当字符串很长时会超时。
class Solution { public: string longestPalindrome(string s) { int startLP=0;//记录最长回文子串的起始位置 int maxLen=0;//记录最长回文子串的长度 for(int i=0;i<s.length();i++) for(int j=i+1;j<s.length();j++) { int start=i,end=j; while(start<=end&&s[start]==s[end])//判断该子串是否为回文 { start++; end--; } if(start>end)//若该子串为回文子串 { if((j-i+1)>maxLen) { startLP=i; maxLen=j-i+1; } } } return s.substr(startLP,maxLen); } };
动态规划是暴力法的一个进化版本,我们没必要每一次都重新判断一个子串是否为回文串,可以将一些辅助判断的信息记录下来供后来者使用,这样后面的回文串判断就仅需O(1)的时间,而不是暴力法中的O(n)。若字符串从i..j的子串是回文,则字符串总(i+1)...(j-1)的子串也是回文,由此最长回文子串就可以分解成一序列子问题。
定义P[i, j]=1表示子串s[i,..., j]是回文串,P[i, j]=0表示子串s[i,..., j]是不是回文串,则得到状态方程和转移方程为:
因此,使用动态规划的方法来求解需要额外O(n^2)空间,时间复杂度也为O(n^2)。
在具体实现中,由于判断长度为n的子串是否为回文串需要借助长度为(n-2)的子串是否为回文串来判断,因此自底向上进行计算,初始化长度为1和长度为2的子串的P[i, j],然后依次计算长度为3,4,... ,s.length()的子串的P[i, j],计算过程同时更新最长回文子串的起始位置和长度。代码如下:
class Solution { public: string longestPalindrome(string s) { int P[s.length()][s.length()]={0}; int startLP=0;//记录最长回文子串起始位置 int maxLen=0;//记录最长回文子串长度 for(int i=0;i<s.length();i++) { P[i][i]=1; startLP=i; maxLen=1; } for(int i=0;i<s.length()-1;i++) { if(s[i]==s[i+1]) { P[i][i+1]=1; startLP=i; maxLen=2; } } for(int len=3;len<=s.length();len++) { for(int i=0;i<s.length()-len+1;i++) { int j=i+len-1; if(s[i]==s[j]&&P[i+1][j-1]==1) { P[i][j]=1; startLP=i; maxLen=len; } } } return s.substr(startLP,maxLen); } };(这里有一个小疑惑,一样的思路,我用C#写的时候,当字符串过长时会发生Memory Limit Exceeded,但是重新用C++写了之后,就不出现这样的问题了,这是为什么?好吧,原谅我这放荡不羁的编程语言,一会儿用这,一会儿用那~)
3. 中心扩展法
中心扩展法的基本思想是依次以每一个字符为中心,向两边扩展,查找以这个字符未中心的最长回文串,从而找到最终的最长回文串。需要注意的是回文串长度为单数和双的情况,例如:“abcba"和”abba",因此在向两边扩展时需要考虑以该字符为中心的扩展,以及以该字符及其下一个字符为中心的扩展,这样才能包含回文串长度为双数的情况。
中心扩展法需要遍历一遍字符串对每一个字符进行扩展,扩展的时间复杂度为O(n),由此得到中心扩展法的时间复杂度为O(n^2),空间复杂度为O(1)。代码如下:
class Solution { public: string expansion(string s,int start1,int start2) { int l=start1,r=start2; while(l>=0&&r<s.length()&&s[l]==s[r]) { l--; r++; } return s.substr(l+1,r-1-l); } string longestPalindrome(string s) { if(s.length()==1) return s; string lp=""; for(int i=0;i<s.length()-1;i++) { string tempStr=expansion(s,i,i); if(tempStr.length()>lp.length()) lp=tempStr; tempStr=expansion(s,i,i+1); if(tempStr.length()>lp.length()) lp=tempStr; } return lp; } };
4.Manacher算法
......明天继续