求解最长回文子串的方法很多,有几种常见的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