最长回文子串(Manacher算法)

枚举中心位置法:

 1 //枚举中心位置法
 2 #include<iostream>
 3 #include<string.h>
 4 using namespace std;
 5 
 6 const int MAXN = 1e5;
 7 
 8 int LongestPalindrome(char *s, int len)
 9 {
10     int i, j;
11     if (s == NULL || len < 1)
12         return 0;
13     int max = 0;
14     int c;
15     for (i = 0; i < len; i++) //枚举中心位置
16     {
17         for (j = 0; (i - j >= 0) && (i + j < len); j++)   //长度为奇数的情况
18         {
19             if (s[i - j] != s[i + j])
20                 break;
21             c = j * 2 + 1;
22         }
23         if (c > max)    //更新max
24             max = c;
25         for (j = 0; (i - j >= 0) && (i + j + 1 < len); ++j)   //长度为偶数的情况
26         {
27             if (s[i - j] != s[i + j + 1])
28                 break;
29             c = j * 2 + 2;
30         }
31         if (c > max)
32             max = c;
33     }
34     return max;
35 }
36 int main()
37 {
38     char s[MAXN];
39     while(cin >> s)
40     {
41         cout << LongestPalindrome(s, strlen(s)) << endl;
42     }
43     return 0;
44 }

 枚举中心位置中,我们需要特别考虑字符串的长度是奇数还是偶数,所以导致我们在编写代码实现的时候要把奇数和偶数的情况分开编写,而Manacher算法则不用分奇偶来讨论

而且上面这种枚举中心位置往两边找的算法时间复杂度是O(n2),下面介绍的“马拉车”算法时间复杂度是O(n)

Manacher算法:

  普通的枚举中心位置的算法需要区分考虑字符串的长度的奇偶性,而Manacher算法则不用区分长度奇偶问题,它的做法是在字符的两边插入一个特殊字符,比如abab转换成#a#b#a#b#,aba转换成了#a#b#a#。为了避免数组越界的问题,还可以在字符串的两端加入特殊的符号。

  Manacher算法使用了一个辅助数组,暂且叫它 P [ ] ,代表的是以 s [ i ] 为中心的最长回文子串往一个方向的拓展长度(这里让它包括 s [ i ] 本身)

  S  #  a  #  b  #  b  #  a  #  b  #  c  #  b  #  a  #
  P  1  2  1  2  5  2  1  4  1  2  1  6  1  2  1  2  1

  观察一下,发现最大的 p [ ] 值减1正好等于原字符串的最长回文子串的长度,所以要知道最长回文子串的长度只需要把 p [ ] 计算出就行了。所以接下来讨论怎么计算p数组

  要计算p数组,先引入几个参数的概念,id:表示目前已知的最长回文子串的中心位置下标;mx:mx = id + p [ id ] ,表示这个回文子串的右边界(即这个回文子串的最右元素的下一位)

  然后核心部分是:如果mx > i,那么P [ i ]  >= min(P [ 2 * id - i ], mx - i)。不理解的话再看看下面两张图就懂了,具体实现看下面代码

 

 

 1 #include<iostream>
 2 #include<string>
 3 #include<cstring>
 4 using namespace std;
 5 
 6 const int maxn = 1e5 + 10;
 7 string s;
 8 string new_s;
 9 int p[maxn];
10 
11 string pre(string s)    //初始字符的转换
12 {
13     int len = s.length();
14     string ret = "$";    //下标0是$,防止数组越界
15     for(int i = 0; i < len; i++)
16     {
17         ret += "#" + s.substr(i, 1);
18     }
19     ret += "#@";    //最后加一个符号,防止数组越界
20     return ret;
21 }
22 
23 int Manacher()
24 {
25     new_s = pre(s);
26     int len = new_s.length();
27     int max_len = -1;    //最长回文子串的长度
28     int id, mx = 0;
29     //算法核心
30     for(int i = 1; i < len; i++)
31     {
32         p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
33         while(new_s[i + p[i]] == new_s[i - p[i]])
34             p[i]++;
35         if(i + p[i] > mx)    //尽可能让mx靠右
36         {
37             mx = i + p[i];
38             id = i;
39         }
40         max_len = max(max_len, p[i] - 1);
41     }
42 
43     return max_len;
44 }
45 int main()
46 {
47     while(cin >> s)
48     {
49         cout << "s = " << s << endl;
50         cout << "new_s = " << pre(s) << endl;
51         cout << "max_len = " << Manacher() << endl;
52     }
53     return 0;
54 }

 Reference:

https://segmentfault.com/a/1190000008484167

https://www.felix021.com/blog/read.php?2040

posted @ 2018-11-19 21:27  Piccolo_Devil  阅读(223)  评论(0编辑  收藏  举报