求最大回文子串(Manacher演算法)
這個知識點我第一天聽時,完全不懂,後來慢慢的看一個pdf文件和請教一個學長才有點懂得,到今天我繼續看一篇部落格,才對最大回文子串有清晰的理解,所以上面有什麼不對的請給位積極指出。
求最大回文子串,我個人覺得其實就是一種想法(它用到了動態規劃的思想),還不算一種單獨的演算法。。。。。。
首先,迴文串是一個正讀和反讀都是一樣的字串,比如abba,abcdadcba等等
迴文子串就是在一個字串中滿足迴文串概念的一個或者是多個子串(包括它自身)
最簡單最直接的想法就是從頭到尾遍歷,分別以每一個字元為中心向兩側擴充套件,算出以每一個字元下為中心所包含的迴文串的個數(包括自身的)。雖然這種方法很好想,但是它的時間複雜度為O(n^2),這個時間是很低的。
所以,我們就想到了一種只需要O(n)的時間複雜度的方法
用這種方法求最大回文子串,首先需要對原字串進行預處理,因為在以前求迴文串時,需要判斷奇偶的情況。預處理就是在一串字串中每相鄰兩個字元之間都新增上一個’#’(字串的最前面和最後面都也要加上一個字元’#’,還有,最前面的最前面的加一個字元是防止在求p[i]時向兩邊擴充套件可能引起陣列越界)
字元新增好後,最重要的一點就是如何求p[i]陣列的值?
首先,p[i]陣列表示的含義是,記錄的以每一個字元為中心的最長迴文串的半徑(以每一個字元為中心能組成的迴文子串的個數),也就是記錄以S[I]為中心的最長的迴文子串的半徑)
P[i]陣列的求法,要借鑑動態規劃的思想
舉例:求abaab的最大回文子串?
首先,我們先定義兩個變數,一個maxid表示在求i之前的迴文串中,能延伸到最右端的位置,同時好有一個id記錄取這個maxid的id值
Maxid=p[id]+id
分兩種情況:
1、 第i個位置不在前面的任何迴文串中,即maxid<i,則初始化p[i]=1,然後左右延伸,就是while(s[p[i]+i]==s[i-p[i]])這句話
2、 如果maxid>i,那麼就不是初始化p[i]=1,就要由迴文串的性質,在id位置前還應該有一個j位置,它是關於id位置對稱。可以對稱到i位置
如圖:
或者是
其實,就是把它們看成一個一維的座標來看,
所以,p[i]又可以分出3種情況:
1、以j為中心的迴文串的範圍有一部分在id為中心的迴文串範圍之外,所以推出,maxid-i=p[i]-k(k為i和maxid之間的距離)
2、以j為中心的迴文串的範圍全部在id為中心的迴文串範圍之內,所以推出,p[i]=p[j]
3、j的迴文串的左端部分與id的迴文串的左端部分重疊,即j-p[j]=i-p[i],所以p[j]=p[i],並且p[i]可以繼續增加,需要while(s[p[i]+i]==s[i-p[i]];p[i]++;這句話來控制
程式碼:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
char s1[N],s2[2*N];
int p[2*N];
void Init(int len)
{
s2[0]='*',s2[len*2+1]='#';
for(int i=0;i<len;i++)
{
s2[i*2+1]='#';
s2[i*2+2]=s1[i];
}
}
int Solve(int len)
{
int maxid=0,maxl=0,id;
for(int i=1;i<=len*2+1;i++)
{
if(maxid>i)
p[i]=min(p[2*id-i],maxid-i);
else
p[i]=1;
while(s2[i+p[i]]==s2[i-p[i]])
p[i]++;
if(p[i]+i>maxid)
{
maxid=p[i]+i;
id=i;
}
if(p[i]>maxl)
maxl=p[i];
}
return maxl-1;
}
int main()
{
while(~scanf("%s",s1))
{
int len=strlen(s1);
Init(len);
int l=Solve(len);
printf("%d\n",l);
}
return 0;
}