最长回文字串--Manacher算法

最长回文字串–Manacher算法

求解一个字符串中最长的回文字串

  1. 暴力法
      首先给定一个字符串str,设其长度为n,我们可以通过比较s[0]和s[n-1],s[1]和s[n-2]…来判断str是否为回文。
      如果我们要得到一个字符串text中最长的回文子串的话,通过枚举text的所有子串subText,然后判断其是否为回文,统计最长的一条即可。
      一个长度为n(n>=2)的字符串,其长度为i的子串有n-i+1个,i=2~n,所以枚举的复杂度为 O(n2)
      
  2. 中心扩展法
     由于奇数长度的回文一定有一个中心字符,所以可以以某个字符串为中心向两边扩展,得到以这个字符为中心的最长回文子串。
     这样的方法有两个问题。
     问题一:偶数长度的回文串需要另外处理。
     因为偶数长度回文没有中心字符。可以通过在每个字符串之间添加特殊字符(不在字符串的字母表中)进行处理,然后我们用数组r来记录以第i个字符为中心的最长回文半径(包括自身,这样可以使得i+r[i]为第一个不为回文的字符)。例如:

    abba-># a # b # b # a #
    index:0 1 2 3 4 5 6 7 8
    r:    1 2 1 2 5 2 1 2 1
    r-1:  0 1 0 1 4 1 0 1 0
    
    babab-># b # a # b # a # b #
    index: 0 1 2 3 4 5 6 7 8 9 10
    r:     1 2 1 4 1 6 1 4 1 2 1
    r-1:   0 1 0 3 0 5 0 3 0 1 0

     这样偶数长度的回文子串变成了以#为中心的奇数长度的回文字串。而奇数长度的回文字串依然是奇数长度。而且r-1即为原串中最长回文串的长度。
     问题二:在扩展时,重复遍历了已经确定为回文的子串。

    text: b a b a b a b a b
    index:0 1 2 3 4 5 6 7 8
    iter1: *ptr=b, queried indices: 1
    iter2: *ptr=a, queried indices: 0, 2
    iter3: *ptr=b, queried indices: 1, 3; 0, 4
    iter4: *ptr=a, queried indices: 2, 4; 1, 5; 0, 6
    ...

     在遍历text[3]的时候,从a往两边扩展,但这时候已经知道text[2]的回文半径(包括自身)为3,即a在text[2]的回文半径之内,然后text[1]的回文半径为2,由于text[2]为中心对称点,那么text[3]的回文半径至少为2(即 r[3]r[1] ),所以iter4中,对index=2, 4的遍历是多余的,从1,5开始遍历即可。
     
    3.Manacher算法

     我们以递归的方式来思考这个问题,即已经求出r[0:i-1]时,如何求解r[i]?
     
     我们令pos为substring(0:i-1)中,其的回文半径能覆盖到最右边的中心字符的位置(即pos + r[pos]最大)(不是r[pos]最大,因为我们需要已知是回文的字符串尽量覆盖到i)。
     
     首先,令mostRight = pos + r[pos],即我们知道的第一个不是回文的位置。当 imostRight 的时候,我们已经求解的信息对我们来说没有任何帮助,此时需要一个一个向两边遍历(例如上文中的iter2)。
     
     但如果有 i<mostRight ,我们可以利用已经求解的信息来确定r[i]的一个下界。
     
      令j 为i以pos为中心对称的位置,那么有 j=pos(ipos)=2posi 。此时r[j]是知道的,而且我们有text[i]=text[j],所以如果text[j - k: j + k](如果j-k小于pos - r[pos])为回文,那么text[i-k:i+k]必然也是回文,所以有 r[i]r[j]
      如果j-k大于pos - r[pos],那么超出pos - r[pos]左边的字符与pos + r[pos](也就是mostRight)右边的字符不一样,那么我们只能确定text[i-k’+1:mostRight-1]为回文(k’=mostRight-i),所以我们有: r[i]min{r[2posi],mostRighti} 。确定下届之后再往两边搜索即可。
     
     

#include
#include
#include
#include

using namespace std;


vector<int> radio;
string copied;

int min(int a, int b) {
    return a < b ? a : b;
}

int findLPS(const string& text) {
    copied.resize(2 * text.size() + 2, '#');
    copied[0] = '$';
    for (int i = 0; i < text.size(); ++i) {
        copied[2 * i + 2] = text[i];
    }
    int len = copied.size();
    radio.resize(len, 1);
    int maxRadioPos = 0; // 记录其回文半径能触及到最远位置的中心点位置
    int maxRadio = 1;
    for (int i = 1; i < len; ++i) {
        int mostRight = maxRadioPos + radio[maxRadioPos];
        if (i < maxRight)
            radio[i] = min(radio[2 * maxRadioPos - i], mostRight - i);
        else
            radio[i] = 1;
        while (i - radio[i] >= 0 && i + radio[i] < len && copied[i + radio[i]] == copied[i - radio[i]])
            ++radio[i];
        if (maxRadio < radio[i]) 
            maxRadio = radio[i];
        if (maxRadioPos + radio[maxRadioPos] < i + radio[i])
            maxRadioPos = i;
    }
    return maxRadio - 1;
}

int main() {
    int  num;
    cin >> num;
    string text;
    for (int i = 0; i < num; ++i) {
        cin >> text;
        int n = findLPS(text);
        cout << n << endl;
    }

    return 0;
}

你可能感兴趣的:(hiho刷题日记)