最长回文子串 manacher算法

来源:互联网 发布:java 并发和线程 编辑:程序博客网 时间:2024/06/05 02:30

求最长回文子串比较有名的一种算法,复杂度是O(n)的,(不要问我为什么是O(n))。

思路:尽量利用到之前遍历得到的回文信息。
表示以i为中心,最长的回文子串的半径有多长,并保存当前的回文子串往右延伸,最长能延伸到哪,即p[i]+i的最大值,存为right,其回文子串的中心点存为index。
考虑当下标为i时,要求p[i]。有几种情况:
1. 当i > right时,超出了之前得到的信息的范围,只能从i开始向两边遍历,求最长的回文范围了;
这里写图片描述
2. 当i < right时,说明现在还在之前遍历过的范围内,那么就把之前的信息利用起来。
这里写图片描述
p[i]最大可能为多大? 考虑一下以index为对称中心的i ‘,如果i ’ - p[i ‘] > left,也就是说i ‘为中心的最长回文子串完全被left-right包含,那么因为是对称的,所以i为中心的最长回文串也就是p[i ‘];如果i ’ - p[i ‘] < left,也就是说i ’ 为中心的最长回文子串超出了left-right的范围,那么以i为中心的回文串能超出right的范围吗。答案是不能,看一下图就知道如果超出了,两边又是对称的,那么left-right就延长了,而left-right已经是以i为中心的最长回文子串了,所以p[i] = min{p[i ‘], right - i}。

上面的方法,回文串必须是奇数长的,因为有中心点嘛,为了代码好些,普遍采用的放大是加特殊字符,将偶数长的也变成奇数长的。
这里写图片描述
代码如下:

class Solution {public:    string pre(string s)    {        string res = "$";        for (int i = 0; i<s.size(); i++)        {            res += "#";            res += s[i];        }        res += "#";        res += "\0";        return res;    }    string longestPalindrome(string s) {        s = pre(s);        //        cout << s << endl;        int p[100000] = { 0 };        int right = 0, index = 0;        int max = 0;        int max_i = 0;        for (int i = 1; i<s.size(); i++)        {            if (right > i)                p[i] = min(p[2 * index - i], right - i);            else                p[i] = 1;            while (s[i + p[i]] == s[i - p[i]])                p[i]++;            if (i + p[i] > right)            {                right = i + p[i];                index = i;            }            if (p[i]>max)            {                max = p[i];                max_i = i;            }        }        string res = "";        for (int i = max_i - p[max_i] + 1; i<max_i + p[max_i]; i++)            if (s[i] != '#')                res += s[i];        return res;    }};