Manacher

流程:

步骤1:预处理原字符串

先对原字符串进行预处理,预处理后得到一个新的字符串,这里我们称为S,为了更直观明了的让大家理解Manacher的流程操作,我们在下文的S中不显示特殊字符(这样并不影响结果)。

步骤2:R和C的初始值为-1,创建半径数组pArr

这里有点与概念相差的小偏差,就是R实际是最右边界位置的右一位。

步骤3:开始从下标 i = 0去遍历字符串S

分支1:i > R ,也就是i在R外,此时没有什么花里胡哨的方法,直接暴力匹配,此时记得看看C和R要不要更新。

分支2:i <= R,也就是i在R内,此时分三种情况,在讨论这三个情况前,我们先构建一个模型

L是当前R关于C的对称点,i'是i关于C的对称点,可知 i' = 2*C - i,并且我们会发现,i'的回文区域是我们已经求过的,从这里我们就可以开始判断是不是可以进行加速处理了

情况1:i'的回文区域在L-R的内部,此时i的回文直径与 i' 相同,我们可以直接得到i的回文半径,下面给出证明

红线部分是 i' 的回文区域,因为整个L-R就是一个回文串,回文中心是C,所以i形成的回文区域和i'形成的回文区域是关于C对称的。

情况2:i'的回文区域左边界超过了L,此时i的回文半径则是i到R,下面给出证明

首先我们设L点关于i'对称的点为L',R点关于i点对称的点为R',L的前一个字符为x,L’的后一个字符为y,k和z同理,此时我们知道L - L'是i'回文区域内的一段回文串,故可知R’ - R也是回文串,因为L - R是一个大回文串。所以我们得到了一系列关系,x = y,y = k,x != z,所以 k != z。这样就可以验证出i点的回文半径是i - R。

情况3:i' 的回文区域左边界恰好和L重合,此时i的回文半径最少是i到R,回文区域从R继续向外部匹配,下面给出证明

因为 i' 的回文左边界和L重合,所以已知的i的回文半径就和i'的一样了,我们设i的回文区域右边界的下一个字符是y,i的回文区域左边界的上一个字符是x,现在我们只需要从x和y的位置开始暴力匹配,看是否能把i的回文区域扩大即可。

总结一下,Manacher算法的具体流程就是先匹配 -> 通过判断i与R的关系进行不同的分支操作 -> 继续遍历直到遍历完整个字符串

时间复杂度:

我们可以计算出时间复杂度为何是线性的,分支一的情况下时间时间复杂度是O(n),分支二的前两种情况都是O(1),分支二的第三种情况,我们可能会出现O(1)——无法从R继续向后匹配,也可能出现O(n)——可以从R继续匹配,即使可以继续匹配,R的值也会增大,这样会影响到后续的遍历匹配复杂度,所以综合起来整个算法的时间复杂度就是线性的,也就是O(n)。

带注释代码

#include<bits/stdc++.h>
using namespace std;
int manacher(string str) {
    if(str.size()==0) return 0;
    int len=(int)(str.length()*2+1);//manacher字符串的长度
    string ma_str;//manacher化的字符串
    int parr[len];//记录每个位置的回文半径
    for(int i=0;i<str.size();i++) ma_str=ma_str+'#'+str[i];
    ma_str=ma_str+'#';
    cout<<ma_str<<endl;
    int R=-1;//最右回文边界
    int C=-1;//R对应的最左回文中心
    int maxn=0;//maxn是记录的最大回文半径
    for (int i=0;i<len;i++){
        //第一步直接取得可能的最短的回文半径,当i>R时,最短的回文半径是1,
        //反之,最短的回文半径可能是i对应的i'的回文半径或者i到R的距离
        //取最小值后开始从边界暴力匹配,匹配失败就直接退出
        parr[i]= R>i ? min(R-i,parr[2*C-i]) : 1;
        while( i+parr[i]<len && i-parr[i]>-1 ){
            if(ma_str[i+parr[i]]==ma_str[i-parr[i]])parr[i]++;
            else break;
        }
        if(i+parr[i]>R){//观察此时R和C是否能够更新
            R=i+parr[i];
            C=i;
        }
        maxn=max(maxn,parr[i]);//更新最大回文半径的值
    }
    return maxn-1;
    //ma_str的长度和原字符串不同,得到的最大回文半径其实是原字符串的最大回文子串长度加1
}

压行纯代码

#include<bits/stdc++.h>
using namespace std;
int manacher(string str) {
    if(str.size()==0) return 0;
    int len=(int)(str.size()*2+1);
    int parr[len],R=-1,C=-1,maxn=0;
    string ma_str;
​
    for(int i=0;i<str.size();i++)
        ma_str=ma_str+'#'+str[i];
    ma_str=ma_str+'#';
    cout<<ma_str<<endl;
​
    for(int i=0;i<len;i++){
        parr[i]= R>i ? min(R-i,parr[2*C-i]) : 1;
        while( i+parr[i]<len && i-parr[i]>-1 ){
            if(ma_str[i+parr[i]]==ma_str[i-parr[i]])parr[i]++;
            else break;
        }
        if(i+parr[i]>R){
            R=i+parr[i];
            C=i;
        }
        maxn=max(maxn,parr[i]);
    }
    return maxn-1;
}

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值