Manacher算法

 

又名马拉车算法,可以在事件复杂度O(n)的情况下求解一个字符串每个回文中心的最长回文半径

暴力匹配:

从原字符串的收不开始,一次向尾部进行遍历,没访问一个字符,就以此字符为中心向两边扩展,记录改点的最长回文长度。

弊端:

1:不适合偶数回文串:abba

2:时间复杂度比较高O(n^2)

 

Manncher算法本质上也是基于暴力匹配的方法,不过做了一点简单的预处理,且在扩展时提供了加速。

预处理:在字符串每个字符两边加了’#’使字符串变成了奇数个字符串,长度变为了2n+1.预处理后的最长回文字符串的长度整数除以2就得到了原先字符串的最长回文串的长度。

当以’#’为中心遍历就解决了偶数回文串的问题。

 

回文半径数组radius是用来记录以每个位置的字符为回文中心求出的回文半径长度。所以#a#c#b#b#c#b#d#s#,所以radius数组为[1,2,1,2,1,2,5,2,1,4,1,2,1,2,1,2,1]

 

一个位置最右回文右边界指的是这个位置及之前的位置的回文子串,所到达的最右边的地方。比如对于字符串#a#c#b#b#c#b#d#s#

 

其实就是回文串最右边的下标。

最开始的时候R=-1.到p=0的位置,回文就是其本身,最右会问边界R=0;p=1时,有回文串#a#,R=2;p=2时,R=2;P=3时,R=6;p=4时,最右回文边界还是p=3时的右边界,R=6;依此类推。

最右回文右边界的对称中心C就是p=4时,R=6,C=3

 

算法流程

分为两种情况

情况一:下一个要移动的位置在最右回文右边界 R的右边

比如在最开始的时候R=-1,p的下一个移动的位置为p=0,p=0在R=-1的右边;p=0时,此时R=0,p的下一个移动位置为p=1,也在R=0的右边

在这种情况下,采用普遍的解法,将移动的位置设为对称中心,向两边暴力扩展,同时更新回文半径数组、最右回文右边界R和最右回文右边界的对称中心

 

情况二:下一个要移动的位置就是最右回文右边界R或是在R的左边。又可分为3种情况

1)cL<pL。p2是p1以c为对称中心的对称点; pL是以p2为对称中心中心的回文子串的左边界;cL是 以C为对称中心的回文子串的左边界。

这种情况下,p1的回文半径就是p2的回文半径radius[p2]

2)cL>pL

这种情况下,p1的回文半径就是p1到R的距离,R-p1+1

3)cL=pL.这种情况下p1的回文半径就换药继续往外扩,但是只需要从R之后往外扩就可以了,扩了之后 更新R和C

 

整个过程的复杂度为O(n)

 

 

预处理

i

int Init(){
    int len=strlen(s);
    s_new[0]='$';//类似于设置左边界
    s_new[1]='#';
    int j=2;
    for(int i=0;i<len;i++){
        s_new[j++]=s[i];
        s_new[j++]='#';
    } 
    s_new[j]='\0';//这个一定不能忘
    return j;//返回s_new的长度 
}
View Code

 

 

处理函数

int Manacher(){
    int len=Init();//取得新字符串长度并完成向s_new的转换
    int max_len=-1;//最长回文长度
    int id;//C 左右回文右边界的对称中心 
    int mx=0;//最右回文右边界
    for(int i=1;i<len;i++){
        if(i<mx) p[i]=min(p[2*id-i]);
        else p[i]=1;
        while(s_new[i-p[i]]==s_new[i+p[i]]) p[i]++;
        if(mx<i+p[i]) {
            id=i;
            max=i+p[i];
        }
        max_len=max(max_len,p[i]-1);
    } 
    return max_len;
} 
View Code

 

 

posted @ 2021-03-16 17:04  白菜茄子  阅读(81)  评论(0编辑  收藏  举报