1. 程式人生 > 實用技巧 >Manacher演算法

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種情況:

  1. right+r <= left+R && right-r >=left (以right為中心的迴文串在區間left~left+R之間)

    我們這裡先給出r段關於R中心點所對稱的線段r1

    len[r] = len[r1] = len[2*R-r]

  2. right+r < left+R && right-r < keft

    還是和1一樣,我們先畫出關於R中心點對稱的r1

    len[r] = len[r1] = len[2*R-r]

  3. 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;
    }
}