1. 程式人生 > >manacher演算法(最長迴文串)

manacher演算法(最長迴文串)

這是建立部落格記錄的第一個程式碼。

題目解釋:

    子串:小於等於原字串長度由原字串中任意個連續字元組成的子序列

    迴文:關於中間字元對稱的文法,即“aba”(單核)、“cabbac”(雙核)等

    最長迴文子串:1.尋找回文子串;2.該子串是迴文子串中長度最長的。

    這是一個求最長迴文子串的問題,在做題的時候有人推薦,然後找了幾篇看,人蠢沒看懂。。。。。

    下面這一篇算是我認為講的最清楚的。處理過程就不寫了,只要原理。

首先要知道這個演算法是用來在O(n)的時間裡看一個字串的最長迴文子串的長度是什麼。

其次,它的核心原理還是動態規劃。

既然是動態規劃,那麼一定要找子狀態!這一點很重要。幾乎所有的動態規劃,你明白子狀態是什麼,子狀態是如何組成下一個狀態的,就等於搞清楚這個演算法。

那麼我們先來這樣看,假如我們知道一個數組p,p[n]代表的是從n這個位置為中心的子串,它的右邊一半的長度,換句話說:2*p[n]是以n處為中心的迴文子串的長度。

這裡注意這個p[n]包括了一開始加進去的#,所以實際上去掉#後的迴文串長度就是p[n],不需要乘2了。

那麼根據動態規劃的定義,要求這麼一個數組,就是如下的方法,

如果要求p[i],那麼我們一定已經知道了小於i的所有值(也就是說如果j<i,那麼p[j]一定知道了,並且是個不會再變的值),而且,還要利用之前的p[j]去求p[i]。

那麼如何設計這個演算法呢?你必須首先知道如何用p[j]來求p[i](從0到i的這i個j,哪個是要用的)。

我們用圖片說明他們的關係:

preview

解釋一下

i是目前要求的中心的位置,我們要求p[i]。

m是從p[0]+0到p[i-1]+i-1這i個值中最大的那個,它可以在i左邊,也可以在i右邊,並不確定。

c是m對應的位置,p[c]+c=m。

j和i關於c對稱。

按照之前的假設,p[j] p[c] m 這三個值都已知了,都是在計算小於i的所有情況時儲存的。

下面就是核心邏輯,

如果m在i左邊,那沒什麼好說的,用expand from center的方法,以i為中心,向兩邊擴充套件,得到p[i]。這裡不需要之前的值。

那麼如果m在i右邊呢? 顯然以c為中心的迴文串包括了i位置。

注意,以i為中心的迴文串,和以j為中心的迴文串存在著關聯。

p[j]為以j為中心的迴文串的一半的長度。

如果p[j]+i沒有超過m,那麼p[i]=p[j]+i。

如果p[j]+i超過m,那麼只能確定p[i]至少有m-i這麼長,之後的字元超過了p[c]的範圍(m'左邊的和m右邊的不對稱),需要重新檢查。

所以p[i]的值要麼是m-i要麼是p[j],哪個小取哪個。

而j=2*c-i。

以上原作者:胥臨軒
連結:https://www.zhihu.com/question/37289584/answer/370848679

程式:

#include <iostream>
#include <vector>
using namespace std;
const int maxn = 100000;
string s;
vector<char> str;
int p[maxn],ans;
void manacher() {
    int id = 0, mr = 0;
    p[0] = 1;
    for(int i = 1; i < str.size(); i++) {
        p[i] = i < mr ? min(p[2*id - i],mr - i) : 1;
        for( ; str[i + p[i]] == str[i - p[i]]; p[i]++)
            if(p[i] + i > mr) {
                mr = p[i] + i;
                id = i;
            }
    }
}
int main() {
    cin >> s ;
    str.push_back('#');
    for(int i = 0; i < s.size(); i++)         str.push_back('#'),str.push_back(s[i]);
    str.push_back('#');
    manacher();
    for(int i = 0; i < str.size(); i++) {
        ans = max(ans, p[i] - 1);
    }
    cout << ans ;
    return 0;
}