Manacher演算法
1.1 概述
Manacher用於求解最長迴文子串。一般情況下我們可能會想到時間複雜度為on3的暴力列舉,也很容易想到時間複雜度為on2的中心擴充套件法。Manacher演算法是一種能在on的時間複雜度中求得最長迴文子串的演算法,Manacher就是優化後的中心檢測法,和KMP演算法類似,Manacher的思想也是避免"匹配"失敗後的下標回退
1.2 詳解
首先,我們得知道迴文半徑
中心擴充套件法是依次列舉每一個點的迴文半徑,取所有迴文半徑的最大值.噹噹前字元滿足迴文條件的時候,檢測下一個字元,否則返回當前半徑長度,然後迴文半徑長度回退為1開始檢測下一點。剛才我們說過Manacher演算法其實是優化後的中心擴充套件法,也就是避免回退。那麼如何避免回退呢?我們可以參考kmp演算法建造一個數組來記錄回退的最佳位置。len[i]表示以i為中心的迴文串長度為len[i].
先設之前已經匹配好了left的最大回文半徑長為R,然後設當前點right的迴文半徑為r,其中,r的起點一定大於R的起點(因為處理r的時候R已經處理好了)
那麼R和r的關係一定有如下3種情況:
-
right+r <= left+R && right-r >=left (以right為中心的迴文串在區間left~left+R之間)
我們這裡先給出r段關於R中心點所對稱的線段r1
len[r] = len[r1] = len[2*R-r] -
right+r < left+R && right-r < keft
還是和1一樣,我們先畫出關於R中心點對稱的r1
len[r] = len[r1] = len[2*R-r]
-
right+r >left+R
同樣做對稱
有了上面的基礎,我們現在唯一需要查詢的就是黃線部分了,這裡將回文半徑由R繼續擴大就好
到這裡演算法還有侷限性,只能計算奇數串,所以我們還需要將原字串處理一下。相鄰字元間插入一個不在該字串的字符集中的字元,比如$。原串長度為s,則轉換後的串長度為2*s+1,所以必然為奇數。
那麼算出來的len和原字串len的關係呢?len[i]-1就是以i為中心的最大回文長度。
在以i點為中點的迴文長度為(2len[i]-1),半徑為len[i]。設在其中的“#”有x個,其他字元有y個,則:
x + y = 2len[i] - 1
由於在每個字元前後都有一個“#”,顯而易見“#”要比其他的字元多一個,則:
x-y=1
解以上兩個方程得:
y= P[i] - 1
1.3程式碼
這裡只是求出了len[],然後根據自己的需要使用。
package manacherstring;
/**
* JavaTest
*
* @author : xgj
* @description : manacher演算法
* @date : 2020-08-19 12:56
**/
public class ManacherString {
public static char[] stringToCharArray(String s) {
char[] chars = s.toCharArray();
char[] res = new char[chars.length * 2 + 1];
int index = 0;
for (int i = 0; i < res.length; ++i) {
res[i] = (i & 1) == 0 ? '#' : chars[index++];
}
return res;
}
public static int[] manacherArray(String str) {
char[] charArr = stringToCharArray(str);
int[] pArr = new int[charArr.length];
int index = -1;
int pR = -1;
int max = Integer.MIN_VALUE;
for (int i = 0; i < charArr.length; ++i) {
pArr[i] = i < pR ? Math.min(pArr[2 * index - i], pR - i) : 1;
while ((i + pArr[i] < charArr.length) && (i - pArr[i])>=0) {
if (charArr[i + pArr[i]] == charArr[i - pArr[i]]) {
pArr[i]++;
} else {
break;
}
}
//更新迴文右邊界以及迴文中心;
if (i + pArr[i] > pR) {
pR = i + pArr[i];
index = i;
}
max = Math.max(max, pArr[i]);
}
return pArr;
}
}