Manacher算法

8 篇文章 1 订阅
2 篇文章 0 订阅

马拉车(Manacher)算法是在O(n)时间内解决寻找源字符串的最长回文子串S的问题的算法。

朴素算法情况下对于每一个S[i]都要左右遍历其最大回文子串,所以时间复杂度是O(n2)

算法流程分析

        由于回文分为偶回文(比如 abab 长度为4)和奇回文(比如 abcba 长度为5),而在处理奇偶问题比较麻烦,所以这里需要做          个预处理,在字符间插入一个特殊字符(这个字符不能在串里出现),将原串转换统一成奇串。 

        比如原字符串: s =”abbaTNTabcba” 
  插入字符之后:sNew= “$#a#b#b#a#T#N#T#a#b#c#b#a#”(开头的$是为了防止越界,在下面的代码注释中有体现) 

        原串s中含有一个偶回文abba和两个奇回文baTNTab、abcba,插入'#'字符后长度都转换成了奇数,比如:#a#b#b#a#长度为            9、#b#a#T#N#T#a#b#长度为15。 
  算法需要一个与新串sNew等长的辅助数组vector<int> p(sNew.size(),0),其中p[i]表示以sNew[i]为中心的最长回文子串的半径,          若p[i]=1,则该回文子串就是sNew[i]本身。下面我们将新串sNew的最大回文子串半径列出:

例如:以sNew[20]=’c’为中心的最长回文子串半径为6。 


  由于第一个和最后一个字符都是#号,且也需要搜索回文,为了防止越界,由于字符串在结尾有’\0’,所以在字符串开头需要加上非#号字符(为了区分这里用的$)。通过p数组可以找到最大回文子串半径的最大值及其中心位置,就能确定最长回文子串了。接下来的问题是如何求p数组? 

Manacher算法利用开头提到的回文的左边是右边的镜像,让回文串起始的对比位置尽可能的大。 

图1

图2

图注:id为已知的最大回文子串中心的位置,mx是已知最大回文串的右边界,i为当前遍历到字符串的位置。

这里有两种情况讨论: 

      一、mx > i

     

二、mx < i  
  此时镜像对预判位置起不到作用,只能从长度为1开始对比,所以p[i] = 1

又到了激动人心的贴板子环节。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
//返回源字符串S的最长回文子串 
string Manacher(string s){
    //预处理源串 
    string t = "$#";
    for(int i=0; i<s.size(); i++){
        t+=s[i];
        t+="#";
    }
    //新建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)  ;
}
int main(){
	string s="abracadabra";
	string temp=Manacher(s);
	cout<<temp<<endl;
	return 0;
}

copy的o_0123西风show码的,蟹蟹他们。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值