0
点赞
收藏
分享

微信扫一扫

最长回文子串与Manacher算法

律楷粑粑 03-01 23:00 阅读 3
算法

看了很多文章,想了很久终于搞明白了,在这写一篇笔记供后来人学习和自己复习

个人认为讲的不错的文章和视频:

比较易懂的 Manacher(马拉车)算法配图详解 - 知乎 (zhihu.com)

马拉车算法 | Coding Club_哔哩哔哩_bilibili

题目:5. 最长回文子串 - 力扣(LeetCode)

manacher算法:又名马拉车算法,能在o(n)的时间复杂度内找到最长回文子串

模板:

class Solution {
public:
    //p[i]记录回文子串的半径(包含作为对称轴的那位),如:
    //i          0 1 2 3 4
    //newstring  # a # a #
    //p[i]       1 2 3 2 1  其中p[3]的半径为#a#,为3
    //2010的大小根据实际要求的字符串大小改变
    int p[2010] = { 0 };
    //预处理字符串,如:将字符串aa处理为#a#a#
    string newString(string& s) {
        string newstring = "";
        for (int i = 0; i < s.size(); i++) {
            newstring += "#";
            newstring += s[i];
        }
        return newstring + "#";
    }
    int Manacher(string& s) {
        //若为空字符串返回0
        if (s.size() == 0)return 0;
        //预处理字符串
        string newstring = newString(s);
        //right为最右回文边界,center为right对应的最左回文中心
        //maxLen为字符串s的最长回文子串的长度(不是newstring,是s!!!)
        int right = -1, center = 0, maxLen = -1;
        for (int i = 0; i < newstring.size(); i++) {
            //取得可能的最短的回文半径
            //i>right时,最短回文半径是1
            //i<right时,最短回文半径可能是p[center * 2 - i](i对center作对称取得的i'的回文半径)
            //           或i到right的距离
            if (i < right)p[i] = min(p[center * 2 - i], right - i);
            else p[i] = 1;
            //从边界暴力搜索最长回文子串,由于回文串的对称,此处while进行的遍历不会太多
            while (i + p[i]<newstring.size() && i - p[i]>-1) {//防止数组越界
                //假设newstring为 #a#
                //i          0 1 2
                //newstring  # a #
                //p[i]       1 2 1
                // 
                //p[1]=2,当a自身是回文串的时候加上a两边的"#"也会是回文串,
                //a换成"b#b""a#c#a"等任何一个回文串都是一样的规律,
                //所以p[i]的大小会比实际回文串的大小多1
                if (newstring[i - p[i]] == newstring[i + p[i]])p[i]++;
                else break;
            }
            //更新center和right
            if (right < p[i] + i) {
                center = i;
                right = p[i] + i;
            }
            //因为p[i]的大小会比实际回文串的大小多1,所以这里要-1
            maxLen = max(maxLen, p[i] - 1);
        }
        for (int i = 0; i < 5; i++)cout << p[i] << " ";
        cout << endl;
        return maxLen;
    }
};

 在这个模板上可做许多回文串的题目,如:5. 最长回文子串 - 力扣(LeetCode)

 在模板基础上加一个变量记录最长回文子串的起始下标即可

class Solution {
private:
    int p[2010] = { 0 };
    //cen记录最长回文子串的起始下标
    int cen = 0;

    string newString(string& s) {
        string newstring = "";
        for (int i = 0; i < s.size(); i++) {
            newstring += "#";
            newstring += s[i];
        }
        return newstring + "#";
    }
    int Manacher(string& s) {
        string newstring = newString(s);
        int center = 0, right = -1, maxLen = -1;
        for (int i = 0; i < newstring.size(); i++) {
            if (i < right)p[i] = min(p[center * 2 - i], right - i);
            else p[i] = 1;
            while (i + p[i]<newstring.size() && i - p[i]>-1) {
                if (newstring[i - p[i]] == newstring[i + p[i]])p[i]++;
                else break;
            }
            if (right < p[i] + i) {
                center = i;
                right = p[i] + i;
            }
            if (p[i] - 1 > maxLen) {
                maxLen = p[i] - 1;

                //根据回文串的对称性计算得出起始下标
                cen = (center - p[i] + 1) / 2;
            }
        }
        return maxLen;
    }
public:
    string longestPalindrome(string s) {
        int temp = Manacher(s);
        return s.substr(cen, temp);
    }
};

举报

相关推荐

0 条评论