最长回文子串 manacher算法

感情迁移 提交于 2019-12-04 20:15:00

求最长回文子串比较有名的一种算法,复杂度是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;
    }
};
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!