求解最长回文子串的方法很多,有几种常见的O(N^2)的最长回文子串求解方法,比如说枚举中心位置向两边扩展,动态规划等,大部分朋友应该都比较熟悉。
Manacher算法相比于上面两种方法,时间复杂度是O(N),空间复杂度也是O(N),可以说是快速求解决回文子串的利器。下面介绍这一算法的思想,以及在文末给与它的实现。
我们以字符串 "ACBCABBB"为例(只考虑长度为奇数的回文串,待会会说明为何这么做),这里引入回文半径的概念,回文半径是指以该字符为中心的回文串的半径,比如ABA,B字符的回文半径为2,Manacher算法的关键点是利用了之前的计算结果,(KMP算法同样也是巧妙的运用了之前的计算结果,才有了高效的匹配速度), 比如说我计算到了第三个字符,也就是B为中心的最长回文串是ACBCA,其回文半径为3,那么我计算以下一个字符C为中心的最长回文串时,因为C处在B的回文半径之内, 我们已经知道C关于B字符的对称位置的C‘的回文半径了,C‘的回文半径为1,被包裹在了B的回文半径内部,因此以C为中心的回文串就不用计算了,其回文半径必定也是为1。
那么当我计算下一个字符A的中心的回文串时,同样的做法,先看A是否被包含在B的回文半径之内,然而并没有....两者相交了,因此A字符必须一步步计算其回文半径。
还有一种情况,比如说字符串DBCBDBCBHAHA,在计算第七个字符C为中心的回文半径时,C被包裹在D的回文半径内,因此我们知道C关于D字符C‘的回文半径了,但是C‘的回文半径为3,B的回文半径并没有把C‘的回文串全部包含在内,因此C的回文半径还是得重新计算,但是这里依然存在着计算优化,我们知道C的回文半径至少为2,之后的计算可以从2开始计算(根据对称位置C‘的回文半径得来)
算法基本思想就是这样,实际计算过程中我需要创建3个变量,分别是rArr[ lenOfString ],index,iRadius,这三个变量的含义如下:
rArr数组是存储每一位字符的回文半径
index,存储的是当前具有最右位置的回文串的字符的下标(最右位置看下边的解释)
iRadius,存储的是index下标的字符的回文半径
关于最右位置的解释,index和iRadius是配套使用的,所谓的最右位置是指index+iRadus-1的值最大(这个值恰好是index下标的字符为中心的回文串的最右边的字符的下标),
最右位置是需要不断更新的,因为这就是Manacher算法就是需要它来判断当前字符是否需要被重新计算。
回到上面的问题来,回文串有两种情况,一种是奇数,一种是偶数,显然Manacher对于是奇数的情况很好处理,那么如果最长回文串是偶数该如何处理呢?答案就是让它变为奇数,是否有种数学的思想在里面,将一个问题转化为一个已经解决了的问题。
因此,我们需要对字符串进行预处理,预处理的方法就是给它填充某种特定字符,比如字符串ABBA,先预处理成Manacher字符串,我给它填充#号,就变成了#A#B#B#A#,这样就可以保证,不管原先的字符串的最长回文子串是奇数还是偶数,填充了字符后,都将变成一个奇数的回文子串了。(假设原先的回文子串长度为N,对于长度为N的字符串将会给它填充N+1个字符,则它的长度变成了2N+1,即变成了奇数)。
至此,Manacher算法介绍完了。下面是它的实现代码,仅供参考:
//Manacher
string getManacherString(string& str){ /*预处理字符串*/
string res="#";
int i,len=str.length();
for(i=0;i<len;i++)
{
res+=str[i];
res+="#";
}
return res;
}
int MaxBackSubString(string src){
int i,len=src.length();
if(len<2)
return len;
string str=getManacherString(src);
len=str.length();
int max=0;
int* pArr=new int[len];
int index=0,pRadius=0;
for(int i=0;i<len;i++){
if(i>=index+pRadius-1){ /*如果当前字符未被包裹在最右位置之内(如相交时),则需要一步步计算其回文半径*/
int temp=1;
while(i+temp<len&&i-temp>=0){
if(str[i+temp]==str[i-temp])
temp++;
else
break;
}
index=i;
pRadius=temp;
pArr[i]=pRadius;
if(pRadius>max)
max=pRadius;
}
else
{
int otherPos=index-(i-index);
if(otherPos-pArr[otherPos]+1>index-pRadius+1) /*如果其对称位置被包裹在index字符的回文半径之内,则无需计算了*/
{
pArr[i]=pArr[otherPos];
continue;
}
else
{
int temp=otherPos-(index-pRadius+1)+1; /*获取当前字符已经匹配的回文半径,从该回文半径开始计算它的最终回文半径*/
while(i-temp>=0&&i+temp<len){
if(str[i-temp]==str[i+temp])
temp++;
else
break;
}
pRadius=temp;
index=i;
pArr[i]=pRadius;
if(pRadius>max)
max=pRadius;
}
}
}
delete[] pArr;
return (2*max-1)/2; /*因为填充了特殊字符的原因*/
}
int main(int arg1,char** arg2){
/*
char str1[]="today is sunny and all is right";
char str2[]=" al";
char* res=strstr(str1,str2);
if(res!=NULL)
cout<<res<<endl;
else
cout<<"res is null"<<endl;
*/
string str="caccccacd";
int len=MaxBackSubString(str);
cout<<len<<endl;
getchar();
}若有疑问或是发现错误,欢迎留言。
版权声明:本文为博主原创文章,未经博主允许不得转载。
Manacher 算法讲解 O(N)复杂度的 最长回文子串求解
原文地址:http://blog.csdn.net/u011408355/article/details/48068549