题目:
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.
思路:
1.本题目可以在O(n)时间复杂度内完成;
2.本算法采取折叠的思路,对于一个回文字符串来说,可以以中间字符进行折叠,折叠后前段字符串和后段字符串可以完全重合;
3.如图所示:
4.在此图中,设c为一个回文字符串的中心,R为该回文字符串的右边界,L为其左边界,对于任何一点j来说,若想计算以j为中心的回文字符串的右边界,j关于c的对称点i具有重要的参考意义,以一个数组p存储以任何一点k为中心的回文字符串的半径(即其右边界至该点的距离),那么p[j]就是以j为中心的回文字符串的半径;
5.现在考虑以i为中心的回文字符串,若其半径p[i]小于i到L的距离(依照回文字符串对称性,也即j到R的距离),则p[j]>=p[i](如0 4 2 3 2 4 2 3 2 4 2,以第二个4为中心的回文字符串为4 2 3 2 4 2 3 2 4,以第一个3为中心的回文字符串为4
2 3 2 4,第二个3与第一个3根据第二个4对称,以第二个3为中心的回文字符串为2 4 2 3 2 4 2),此外,按照折叠思路,由于以c为中心折叠,[L,C]与[C,R]可以完全重合,那么在[L,R]范围内,[i-p[i],i+[pi]]与[j-[pj],j+p[j]]也可完全重合;
6.若p[i]大于R-j,那么以j为中心,以R-j为半径的字符串必然是对称的;
7.根据4、5步可以快速确定以j为圆心的回文字符串的最小半径,之后具体半径计算则需在此基础上进行再计算,但是最小半径的确定已经节省了大量时间;
8.上述图中j位于R之前,对于j位于R之后的情况,p[i]已没有任何参考意义,所以p[j]需要从0开始计算;
9.按照折叠理论,每一个回文字符串都可以一个中心进行折叠,但是若回文字符串有偶数个字符,则没有所谓的中心点,基于此,我们可以对原始字符串进行修改,以方便可以使任何一个回文字符串拥有奇数个字符,具体方法为在任意两个字符之间插入一个特殊符号(如#);
10.依照上述分析,只要不断更新当前最长回文字符串的中心点C与半径R,使用一个指针j从头遍历字符串一次即可获取字符串的最长回文字符子串,于是,该算法时间复杂度为O(n);
代码:
<span style="font-weight: normal;"><span style="font-size:14px;">string process(string s){
int n = s.length();
if (n == 0) return "^$";
string ret = "^";
for (int i = 0; i < n; i++)
ret += "#" + s.substr(i, 1);
ret += "#$";
return ret;
}
class Solution{
public:
string longestPalindrome(string s){
s=process(s);
int len=s.length();
int *p=new int[len];
int c=0;int r=0;
for(int j=1;j!=len-1;++j)
{
int i=2*c-j;
p[j]=(j>c+r)?0:min(p[i],c+r-j);
while(s[j+p[j]+1]==s[j-p[j]-1])
++p[j];
if(p[j]>r)
{
c=j;
r=p[j];
}
}
int maxlen=0;
int center=0;
for(int i=0;i!=len-1;++i)
{
if(p[i]>maxlen)
{
maxlen=p[i];
center=i;
}
}
delete[] p;
s=s.substr(center-maxlen,2*maxlen+1);
string result;
for(int i=1;i<=s.length()-2;i+=2)
result+=s[i];
return result;
}
};</span></span>
补充:
<span style="font-weight: normal;"><span style="font-size:14px;">string process(string s){
int n = s.length();
if (n == 0) return "^$";
string ret = "^";
for (int i = 0; i < n; i++)
ret += "#" + s.substr(i, 1);
ret += "#$";
return ret;
}
class Solution{
public:
string longestPalindrome(string s){
s=process(s);
int len=s.length();
int *p=new int[len];
int c=0;int r=0;
for(int j=1;j!=len-1;++j)
{
int i=2*c-j;
p[j]=(j>c+r)?0:min(p[i],c+r-j);
while(s[j+p[j]+1]==s[j-p[j]-1])
++p[j];
if(p[j]>r)
{
c=j;
r=p[j];
}
}
int maxlen=0;
int center=0;
for(int i=0;i!=len-1;++i)
{
if(p[i]>maxlen)
{
maxlen=p[i];
center=i;
}
}
delete[] p;
s=s.substr(center-maxlen,2*maxlen+1);
string result;
for(int i=1;i<=s.length()-2;i+=2)
result+=s[i];
return result;
}
};</span></span>
1.在对字符串进行预处理时,为了避免字符串越界,需要在其头部和尾部分别做一个标记,如skyhuskyykspanda,处理后为^#s#k#y#h#u#s#k#y#y#k#s#p#a#n#d#a#$