簡述
Manacher算法,又稱馬拉車算法,它是用於求一個字符串的最長回文子串長度的算法,時間和空間復雜度為O(n)。
算法思想
求一個字符串的最長回文子串長度,我們如果用暴力來做,我們就要取出這個串的所有子串,然后判斷這個子串是不是回文串,復雜度是n方的。
那么馬拉車為何如此神奇能做到O(n)呢?
首先我們來看這兩個串:abba和abcba。第一個串的回文中心在兩個b之間,第二個串的回文中心為字符c,這樣兩種情況我們分類討論太麻煩了,所以我們考慮對原字符串進行一個字符填充,abba->@#a#b#b#a# ,abcba->@#a#b#c#b#a#,這樣他們都變成了奇數,討論的情況就一致了。
設原字符串長度為len1,重構后長度為len2,則len2=len1*2+2且對於s[i],它在重構后字符串的對應下標為i*2+2。
由此我們可以寫出重定義字符串的代碼:
int getstr() {//重定義字符串,s為原字符串,str為新串,len為s串長度,返回新串長度 int k=0,len=strlen(s); str[k++]='@'; for(int i=0;i<len;i++){ str[k++]='#'; str[k++]=s[i]; } str[k++]='#'; return 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 len){//len為str串長度 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; }
模板
#include<iostream> #include<string.h> using namespace std; const int maxn=11000002; char s[maxn<<1],str[maxn<<1]; int num[maxn*2]; int getstr() { int k=0,len=strlen(s); str[k++]='@'; for(int i=0;i<len;i++){ str[k++]='#'; str[k++]=s[i]; } str[k++]='#'; return k; } int manacher(int len){ 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]); else num[i]=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() { int cas=1; while(cin>>s){ if(s[0]=='E') break; int len1=strlen(s); int len2=getstr(); cout<<"Case "<<cas++<<": "<<manacher(len2)<<'\n'; } return 0; }
