求一个字符串的最长回文子串有几种方法:
1、最容易想到的就是遍历整个字符串,然后求出以每个字符为中心的最长回文串(要考虑奇数和偶数问题),这样的时间复杂度为o(n^2);
2、还有一种就是用后缀数组的方法,将字符串倒转过来然后接到原字符串的后面构造一个新串,中间用一个原字符串中没出现的字符做分隔符,求原字符串最长回文字串就相当于求新串后缀数组的最长公共前缀了,代码较复杂,时间复杂度为o(nlgn);
3、另一种方法时用manacher算法:这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。
算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
原串: w aa bwsw f d
新串: # w# a # a # b# w # s # w # f # d #
辅助数组P: 1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1
由于这个算法是线性从前往后扫的。那么当我们准备求P[i]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)
void pa(char *str,int n,int p[])
{
int i;
int mx = 0;
int id=0;
for(i=1; i<n; i++)
{
if( mx > i )
p[i] = MIN( p[2*id-i], mx-i );
else
p[i] = 1;
for(; (i+p[i]<n)&&(i-p[i]>=0)&&(str[i+p[i]] == str[i-p[i]]); p[i]++)
;
if( p[i] + i > mx )
{
mx = p[i] + i;
id = i;
}
}
}
int main(){
int i;
int p[MAXLEN]={0};
char s[]="defabafedcd";
char *s1=(char *)malloc(strlen(s)*2+2);
for(i=0;i<2*strlen(s)+2;i++)
if(i%2)
*(s1+i)=*(s+i/2);
else
*(s1+i)='#';
printf("%s\n",s);
printf("%s\n",s1);
pa(s1,strlen(s1),p);
for(i=0;i<strlen(s1);i++)
printf("%d ",p[i]);
printf("\n");
}