manacher算法

马拉车算法一般用于求最长回文子串的问题,暴力求解最长回文字串的时间复杂度是O(n^2),马拉车就是分析回文子串的特点,减少了暴力的过程,时间复杂度是O(n)。

最长回文子串问题

给一个字符串,求最长回文子串的长度。

暴力解法:

暴力解法的思路就是遍历每一个元素,以该元素为中心,不断向外扩充,直到不能扩充为止,遍历完所有的字符得到最大的。

#include<iostream>
#include<string>
using namespace std;

int main()
{
    string str;
    while(cin >> str)
    {
        int len = str.size();
        int maxLen = 0;
        for(int i=1; i<len; i++)
        {
            //当回文串为偶数个时
            int low = i-1, high = i;
            while(low>=0 && high<len && str[low]==str[high])
            {
                low--;
                high++;
            }
            if(high-low-1>maxLen)
                maxLen = high-low-1;
            
            //当回文串为奇数个时
            low=i-1; high=i+1;
            while(low>=0 && high<len && str[low]==str[high])
            {
                low--;
                high++;
            }
            if(high-low-1>maxLen)
                maxLen = high-low-1;
        }
        cout << maxLen << endl;
    }
    return 0;
}

动态规划解法

  • dp[i][i]也就是长度为1肯定是回文
  • dp[i][i+1]也就是长度为2只有在s[i]==s[i+1]时才是回文
  • 之后从长度为3开始已知检索下去
class Solution {
public:
    string longestPalindrome(string s) {
        int len=s.size();
        if(len==0||len==1)
            return s;
        int start=0;//回文串起始位置
        int max=1;//回文串最大长度
        vector<vector<int>>  dp(len,vector<int>(len));//定义二维动态数组
        for(int i=0;i<len;i++)//初始化状态
        {
            dp[i][i]=1;
            if(i<len-1&&s[i]==s[i+1])
            {
                dp[i][i+1]=1;
                max=2;
                start=i;
            }
        }
        for(int l=3;l<=len;l++)//l表示检索的子串长度,等于3表示先检索长度为3的子串
        {
            for(int i=0;i+l-1<len;i++)
            {
                int j=l+i-1;//终止字符位置
                if(s[i]==s[j]&&dp[i+1][j-1]==1)//状态转移
                {
                    dp[i][j]=1;
                    start=i;
                    max=l;
                }
            }
        }
        return s.substr(start,max);//获取最长回文子串
    }
};

马拉车解法:

马拉车算法涉及一些变量,首先是maxRight代表的是目前的最大右边界,pos是可以达到最大右边界的中心所在的位置,RL是回文半径数组,记录的是每一个中心点的回文半径长度,maxLength回文子串的最大长度。
一次遍历,每次遍历首先判断当前 i 是不是在maxRight的内部,也就是i < maxRight,如果满足,则RL[i] = min(RL[2 * pos - i], maxRight - i);第一个参数的意思是与 i 关于pos对称的另外一点的半径,第二个参数是 i 到最大右边界的距离,最大右边界之外的元素需要暴力扩充才能知道。

  • 用#填充字符串,例如 ab 变成 #a#b# ,这样就不用讨论字符串个数为奇数或偶数的情况了
  • p数组代表回文半径数组,初始为0,id代表当前达到最大右边界的中心位置,mx表达最大右边界的位置,index表示最长回文子串的中心索引位置
  • 从j = 1开始遍历,对于每一个j ,首先判断最大右边界和 j 的位置,若 j 在右边界里面,就找到 j 关于 id 对应的点,求得对应点的回文半径和mx - j长度的较小值,将该值赋给p[ j ],若 j 在最大右边界 mx 外面,则直接将p[ j ]=1。之后只能暴力扩充来判断p[ j ]。
  • 然后更新mx和最大回文长度
  • 通过在首字符前加一个¥,结尾后面再加一个@,可以推出最长回文子串在原串中的位置(index-maxlength)/2,长度为maxlength-1
class Solution {
public:
    string longestPalindrome(string s) {
        //预处理源串 
        string t = "$#";
        for(int i=0; i<s.size(); i++){
            t += '#' + s[i];
        }
        //新建p数组,大小和t串一致,初始化为0 ,p[i]表示以t[i]为中心的回文串半径 
        vector<int> p(t.size() , 0); 
    
        //设定重要参数 mx(某回文串延伸到的最右边下标),id(mx所属回文串中心下标),
        //reCenter(结果最大回文串中心下标),reLen(最大长回文长度) 
        int mx = 0, id = 0, reCenter = 0, reLen = 0;
    
        //遍历t字符串
        for(int i=1; i<t.size(); i++){
            //核心算法 
            p[i] = mx > i ? min(mx - i , p[2*id - i]) : 1;
        
            //上面的语句只能确定i~mx的回文情况,至于mx之后的部分是否对称,就只能老老实实去匹配了,匹配一个p[i]++ 
            while(t[i + p[i]] == t[i - p[i]]) p[i]++;
        
            //当t[i]匹配的 右边界超过mx时mx和id就更新 
            if(i+p[i] > mx){
                mx = i+p[i];
                id = i;
            }
            //更新结果数据 
            if(p[i] > reLen){
                reLen = p[i];
                reCenter = i;    
            }
        }
    
        return s.substr((reCenter - reLen) / 2 , reLen - 1)  ;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值