最长回文字串--Manacher算法

来源:互联网 发布:软件怎么挣钱 编辑:程序博客网 时间:2024/06/06 01:47

最长回文字串–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 8r:    1 2 1 2 5 2 1 2 1r-1:  0 1 0 1 4 1 0 1 0babab-># b # a # b # a # b #index: 0 1 2 3 4 5 6 7 8 9 10r:     1 2 1 4 1 6 1 4 1 2 1r-1:   0 1 0 3 0 5 0 3 0 1 0

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

    text: b a b a b a b a bindex:0 1 2 3 4 5 6 7 8iter1: *ptr=b, queried indices: 1iter2: *ptr=a, queried indices: 0, 2iter3: *ptr=b, queried indices: 1, 3; 0, 4iter4: *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<cstdio>#include<iostream>#include<vector>#include<string>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;}
0 0
原创粉丝点击