• 最长回文子串问题---Manacher算法


    Manacher算法中文名叫马拉车算法。这种算法解决的问题是求最长回文子串,其采用以空间换时间的方式,将时间复杂度优化到了O(N)。
    而一般的中心扩展法时间复杂度则是O(N2)。


    实现思路

    回文字符串按照长度来分,可以分为奇回文(其长度为奇数)、偶回文(其长度为偶数),如果采用中心扩展法,需要分两种情况来寻找回文。马拉车算法为了简化这一步,对原始字符串进行了处理,在字符串左右两边以及字符之间加上特殊字符(这个特殊字符可以是任意的,即使源字符串中已经出现了该字符,因为在构建回文时永远都是特殊字符和特殊字符匹配),让字符串变成一个奇回文。例如:

    原字符串:abba,长度为4
    预处理后:#a#b#b#a#,长度为9
    
    原字符串:aba,长度为3
    预处理后:#a#b#a#,长度为7
    
    • 1
    • 2
    • 3
    • 4
    • 5

    至于源字符串的回文串长度,只需要用处理后的长度除以二就能够得到。

    回文半径以及最远范围

    我们需要开辟一个和预处理后字符串长度相等的数组pArr,用来保存每个位置的回文半径是多长。

    在这里插入图片描述

    另外,回文半径-1就是源字符串以该点为中心的回文串的长度。

    然后,我们就可以通过回文半径求出最远范围R了,比如:
    在这里插入图片描述

    计算pArr数组

    只要求出pArr数组的大小,我们就能够求出以所有位置为中心的回文串的长度了,最长回文子串自然也就能够求出来。

    根据i对称位置i'的半径与R的比较,可以分为以下几种情况:

    1. i'半径完全在R以及R对称范围之内:
      这种情况,i的回文半径与i'完全相同:
      在这里插入图片描述

    想要证明也不难:
    在这里插入图片描述

    1. i'的半径在R的边缘位置:
      在这里插入图片描述

    2. i'的半径超过了R的边缘位置

    在这里插入图片描述

    1. i落在了R的外面
      此时我们直接使用中心扩展法暴力求解。

    通过上面4种情况,我们不难看出,前三种情况大大加快了中心位置i在最远范围内的求解速度。将时间复杂度优化到了O(1)。

    代码实现

    #include<iostream>
    #include<vector>
    #include<algorithm>
    using namespace std;
    
    string manacherString(string s)
    {
        string res;
        for (int i = 0; i < s.size(); i++)
        {
            res += '#';
            res += s[i];
        }
        res += '#';
        return res;
    }
    int longestPalindrome(string s) 
    {
        //预处理字符串
        string str = manacherString(s);
        vector<int>pArr(str.size());
        //最远范围R
        int R = -1;
        //最远范围R的中心点
        int C = -1;
        //最大半径,maxR-1就是最长回文子串
        int maxR = INT_MIN;
        int ansC = 0;
        for (int i = 0; i < str.size(); i++)
        {
            //找i与最远中心点对称的i',并且讨论i'的半径,看看i加上半径后是否超过R
            pArr[i] = R > i ? min(pArr[2 * C - i], R - i) : 1;
            while (i + pArr[i]<str.size() && i - pArr[i]>-1)
            {
                //如果i落在R内部,则直接break,如果在外部,则执行if
                //实行中心扩展法
                if (str[i + pArr[i]] == str[i - pArr[i]])
                {
                    pArr[i]++;
                }
                else
                {
                    break;
                }
            }
            //修改R的边界
            if (i + pArr[i] > R)
            {
                R = i + pArr[i];
                C = i;
            }
            maxR = max(maxR, pArr[i]);
        }
    
        return maxR - 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    在这里插入图片描述


    几种可以用Manacher算法求解的题型

    5. 最长回文子串

    5. 最长回文子串
    只要是求回文子串,都可以用Manacher算法求解。

    这道题与上面的求长度类似,这道题需要求出最长的回文串。我们再用两个变量记录最长半径以及最长半径的中心点,就能够求出来了。

    class Solution {
    public:
        string longestPalindrome(string s) {
            string str=manacherString(s);
            vector<int>pArr(str.size());
            int R=-1;
            int C=-1;
            int max=INT_MIN;
            //ansC记录max的中心位置
            int ansC=0;
            for(int i=0;i<str.size();i++)
            {
                pArr[i]=R>i?min(pArr[2*C-i],R-i):1;
                while(i+pArr[i]<str.size()&&i-pArr[i]>-1)
                {
                    if(str[i+pArr[i]]==str[i-pArr[i]])
                    {
                        pArr[i]++;
                    }
                    else
                    {
                        break;
                    }
                }
                //修改R的边界
                if(i+pArr[i]>R)
                {
                    R=i+pArr[i];
                    C=i;
                }
                if(max<pArr[i])
                {
                    max=pArr[i];
                    ansC=C;
                }
            }
    
            string res;
            //根据推导,(中心位置-(半径-1))/2就能得到源字符串中,回文串起始位置的下标
            //max-1就是回文串的长度
            res+=s.substr((ansC-(max-1))/2,max-1);
            return res;
            
        }
        string manacherString(string s)
        {
            string res;
            for(int i=0;i<s.size();i++)
            {
                res+='#';
                res+=s[i];
            }
            res+='#';
            return res;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    214. 最短回文串

    214. 最短回文串
    这道题是在前面添加字符,也可以采用KMP来做。
    Manacher算法既可以解决在前面添加字符的问题,也可以解决在后面添加字符的问题。

    如果是在前面添加字符,我们只需要找包括下标为0的字符的最长回文串,然后在前面添加剩下子串的逆序即可。
    如果是在后面添加,则是包括最后一个字符的最长回文串。

    在这里插入图片描述

    class Solution {
    public:
        string shortestPalindrome(string s) {
            string str=manacherString(s);
            vector<int>pArr(str.size());
            int R=-1;
            int C=-1;
            int max=INT_MIN;
            int ansC=0;
            //keyC和keyR用来记录半径包括下标为0的最大范围
            int keyC=0;
            int keyR=0;
            for(int i=0;i<str.size();i++)
            {
                pArr[i]=R>i?min(pArr[2*C-i],R-i):1;
                while(i+pArr[i]<str.size()&&i-pArr[i]>-1)
                {
                    if(str[i+pArr[i]]==str[i-pArr[i]])
                    {
                        pArr[i]++;
                    }
                    else
                    {
                        break;
                    }
                }
                //修改R的边界
                if(i+pArr[i]>R)
                {
                    R=i+pArr[i];
                    C=i;
                }
                if(max<pArr[i])
                {
                    max=pArr[i];
                    ansC=C;
                    //更新keyC和keyR,因为max是一直在变大的,所以keyR也是一直在变大
                    if(ansC-(max-1)==0)
                    {
                        keyC=ansC;
                        keyR=max-1;
                    }
                }
            }
            //求出回文的范围,然后将剩下的子串逆序加到前面
            int len=(keyC+keyR)/2-(keyC-keyR)/2+1;
            string r=s.substr(len-1);
            reverse(r.begin(),r.end());
            return r+s;
        }
            string manacherString(string s)
        {
            string res;
            for(int i=0;i<s.size();i++)
            {
                res+='#';
                res+=s[i];
            }
            res+='#';
            return res;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
  • 相关阅读:
    在IDEA中搭建Spring5.2.x版本源码(~附带完整过程和图示~)
    人大女王金融硕士项目——不断挑战自己,提升自己就在现在
    MySQL分页查询的5种方法
    算法打卡day52|单调栈篇03| 84.柱状图中最大的矩形
    TCP/IP(五)TCP的连接管理(二)三次握手细节
    [附源码]计算机毕业设计springboot良辰之境影视评鉴系统
    C#操作GridView控件绑定数据实例详解(二)
    HTTP 3的发布
    浅谈Javascript单线程和事件循环
    重温C语言十三------动态内存分配
  • 原文地址:https://blog.csdn.net/qq_52670477/article/details/125401869