• 最长回文子串(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

  • 相关阅读:
    Flume 读取实时更新的日志文件
    一些关于Flume收集日志的资料
    Java Pattern Matcher 正则表达式需要转义的字符
    多播 & multicast
    branch prediction
    一时紧张简单题都没做好,哈
    海量数据求中位数
    继续过Hard题目.0207
    压力工具代码及epoll使用
    C++里面mutable的作用
  • 原文地址:https://www.cnblogs.com/friend-A/p/9973549.html
Copyright © 2020-2023  润新知