最长回文字串--Manacher算法

    xiaoxiao2021-04-02  37

    最长回文字串–Manacher算法

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

    暴力法   首先给定一个字符串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) 。   

    中心扩展法  由于奇数长度的回文一定有一个中心字符,所以可以以某个字符串为中心向两边扩展,得到以这个字符为中心的最长回文子串。  这样的方法有两个问题。  问题一:偶数长度的回文串需要另外处理。  因为偶数长度回文没有中心字符。可以通过在每个字符串之间添加特殊字符(不在字符串的字母表中)进行处理,然后我们用数组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<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; }
    转载请注明原文地址: https://ju.6miu.com/read-665717.html

    最新回复(0)