字符串处理 —— 回文串相关 —— Manacher 算法
【概述】
Manacher 算法又稱馬拉車算法,用于求最長回文子串。
對于最長回文子串傳統的求法的求法是以每個字符為中心,向兩邊尋找回文子串,在遍歷完整個數組后即可得到最長回文子串,其時間復雜度為 O(n^2)
而馬拉車算法,將求最長回文子串的時間復雜度提升到了線性,其時間復雜度只有 O(n)
【算法流程】
1.預處理
由于字符串的長度分為奇偶兩種,因此對于初始的字符串,在每一個字符的左右都加上一個未在串中出現過的字符,得到一個新的字符數組 newStr[]。
比如:
- 奇數字符串:bob --> #b#o#b#
- 偶數字符串:noon --> #n#o#o#n#
這樣一來,無論原字符串是奇數個字符還是偶數個字符,處理后的字符串字符的個數都是奇數個,這樣就無需分情況討論了。
2.設置輔助數組
接下來,還需要定義一個輔助數組 p[],其中 p[i] 表示以字符 newStr[i] 為中心的最長回文半徑,若 p[i]=1,則該回文子串就是 newStr[i] 本身。
例如:以字符串 abbahopxp?為例
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| newStr[i] | $ | # | a | # | b | # | b | # | a | # | h | # | o | # | p | # | x | # | p | # |
| p[i] | ? | 1 | 2 | 1 | 2 | 5 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 4 | 1 | 2 | 1 |
可以看出,p[i]-1 即為原字符串中最長回文串的長度。
3.求解輔助數組
首先新增兩個變量 mx 與 id,其中 mx 代表以 id 為中心的最長回文串的右邊界,即:mx=id+p[id]
假設求以 i 為中心的最長回文半徑,即求解 p[i],當 i<mx 時,那么有 p[i] = min(p[2*id-i], mx-i),否則 p[i]=1,其中 2*id-i 為圖中的 j 點,即 p[j] 是以 j 為中心的最長回文半徑
【復雜度分析】
基于回文串的性質,輔助數組 p[i] 的值基于以下三種情況得出:
1)j 的回文串有一部分在 id 的回文串之外
如上圖,黑線為 id 的回文串,i 與 j 關于 id 對稱,紅線為 j 的回文,紫線為 i 的回文,虛線為以 id 為中心的回文右邊界 mx,那么根據 p[i]=mx-i,即紫線的部分,那么可以得出,p[i] 無法得到更大的值
如上圖,假設右側新增的紫色 d 部分是 p[i] 可增加的部分,那么根據回文的性質,也即是說 id 的回文 = 黑線 + a + d,矛盾,假設不成立,因此 p[i] 無法再增加,得到一個更大的值
2)j 回文串全部在 i 內部
如上圖,此時 p[i]=p[j],那么可以得出,p[i] 無法得到更大的值
如上圖,假設右側的紅線 d 是 p[i] 可增加的部分,那么根據回文的性質,j 的回文 = j 之前的回文 + a + b,矛盾,假設不成立,因此 p[i] 無法再增加,得到一個更大的值
3)i?的回文串右端與 id 的回文串右端重合
如上圖,此時 p[i]=p[j],或 p[i]=mx-i,此時?p[i] 可以繼續增加,那么有:
while(newStr[i-p[i]]==newStr[i+p[i]]) p[i]++;根據(1)(2)(3),容易推出 Manacher 算法的最壞情況,即為字符串內全是相同字符的時候,平均訪問每個字符 5 次,同理,也容易推出 Manacher 算法的最佳情況,即為字符串內字符各不相同的時候,平均每個字符訪問 4 次。
綜上,Manacher算法的時間復雜度為 O(n)。
【實現】
char str[N];//原字符串 char newStr[N*2];//預處理后的字符串 int p[N*2];//輔助數組 int init(){//對原字符進行預處理newStr[0]='$';newStr[1]='#';int j=2;int len=strlen(str);for (int i=0;i<len;i++){newStr[j++]=str[i];newStr[j++]='#';}newStr[j] ='\0'; //字符串結束標記return j;//返回newStr的長度 }int manacher(){int len=init();//取得新字符串長度并完成字符串的預處理int res=-1;//最長回文長度int id;int mx=0;for(int i=1;i<len;i++){int j=2*id-i;//與i相對稱的位置if(i<mx)p[i]=min(p[j], mx-i);elsep[i]=1;//由于左有'$',右有'\0',不需邊界判斷while(newStr[i-p[i]] == newStr[i+p[i]])//p[i]的擴大p[i]++;if(mx<i+p[i]){//由于希望mx盡可能的遠,因此要不斷進行比較更新id=i;mx=i+p[i];}res=max(res,p[i]-1);}return res; }int main(){while (printf("請輸入字符串:\n")){scanf("%s",str);printf("最長回文長度為 %d\n", manacher());}return 0; }?
總結
以上是生活随笔為你收集整理的字符串处理 —— 回文串相关 —— Manacher 算法的全部內容,希望文章能夠幫你解決所遇到的問題。