马拉车算法详解
简述
Manacher算法,又称马拉车算法,它是用于求一个字符串的最长回文子串长度的算法,时间和空间复杂度为O(n)。
算法思想
求一个字符串的最长回文子串长度,我们如果用暴力来做,我们就要取出这个串的所有子串,然后判断这个子串是不是回文串,复杂度是n方的。
那么马拉车为何如此神奇能做到O(n)呢?
首先我们来看这两个串:abba和abcba。第一个串的回文中心在两个b之间,第二个串的回文中心为字符c,这样两种情况我们分类讨论太麻烦了,所以我们考虑对原字符串进行一个字符填充,abba->@#a#b#b#a#0 ,abcba->@#a#b#c#b#a#0,这样他们都变成了奇数,讨论的情况就一致了。
由此我们可以写出重定义字符串的代码:
void getstr() {//重定义字符串,s为原字符串,str为新串,len为s串长度 int k=0; str[k++]=‘@‘; for(int i=0;i<len;i++){ str[k++]=‘#‘; str[k++]=s[i]; } str[k++]=‘#‘; str[k++]=‘0‘; len=k; }
首先我们规定几个变量的含义:len[i]为以i为中心点的最长回文序列的回文半径,po为当前中心点,p为当前回文子串的右边界。
我们可以得出,ans=len[i]-1,因为加入填充字符后的str的最长回文串为2*len[i]-1,又因为#号的个数比原字符多一个,所以#字符的总数为len[i]个,故原字符的最长回文子串为len[i]-1个。
当i<=p时,找到i相对于po的对称点j,如果len[j]<p-i,如下图:
由对称性得知,i和j左右的字符是一样的,所以len[i]=len[j]。
如果len[j]>=p-i,由对称性,说明以i为中心的回文串会扩散至p以外,而大于p部分的串我们还没用匹配,所以我们一个一个匹配,更新po和len[i]。
当i>p时,说明以i为中心的回文串一点都没匹配,就只能老老实实一个一个匹配了。
所以我们可以写出如下匹配部分代码:
int manacher(){ int mx=0,id; int maxx=0; for(int i=0;i<len;i++){ if(i<mx) len[i]=min(mx-i,len[2*id-i]);//i在mx左边,取在边界以内且较小的那段 else len[i]=1;//i在mx右边,所以直接等于1 while(str[i+len[i]]==str[i-len[i]]) len[i]++;//向右一个个匹配 if(i+len[i]>mx){ mx=i+len[i]; id=i; maxx=max(maxx,len[i]-1); } } return maxx; }
模板
#include<iostream> #include<string.h> using namespace std; const int maxn=11000002; char s[maxn<<1],str[maxn<<1]; int len,num[maxn*2]; void getstr() {//重定义字符串,s为原字符串,str为新串,len为s串长度 int k=0; str[k++]=‘@‘; for(int i=0;i<len;i++){ str[k++]=‘#‘; str[k++]=s[i]; } str[k++]=‘#‘; str[k++]=‘0‘; len=k; } int manacher(){ int mx=0,id; int maxx=0; for(int i=0;i<len;i++){ if(i<mx) num[i]=min(mx-i,num[2*id-i]);//i在mx左边,取在边界以内且较小的那段 else num[i]=1;//i在mx右边,所以直接等于1 while(str[i+num[i]]==str[i-num[i]]) num[i]++;//向右一个个匹配 if(i+num[i]>mx){ mx=i+num[i]; id=i; maxx=max(maxx,num[i]-1); } } return maxx; } int main() { cin>>s; len=strlen(s); getstr(); cout<<manacher(); return 0; }