【数据结构】Manacher算法

Manacher


Manacher算法要解决的问题是在一个字符串中寻找最大回文子字符串长度。
该问题的暴力解法是遍历每一个字符,从该字符向两边扩展,寻找边界。
但该方法中,每次扩展得到的信息并不能被充分利用,设一个字符串如下:

在这里插入图片描述
标记为灰色的回文子串中,右侧的’a’‘b’'a’三个字符为中心的最大回文子串长度与左侧的对称点的相应信息是完全相同的,Manacher就是利用这个信息对暴力解法进行了加速。


辅助字符串

回文字符串可能是奇数或偶数个,为了在算法中不考虑奇数和偶数的情况,对原字符串进行扩充:

在这里插入图片描述
在所有字符中间插入一个辅助字符‘#’(可以是任意字符,包括原数组中存在的,不影响结果),统计每个字符位的最大回文半径(右边界减中心点+1),则原字符串中每个字符的最大回文长度即为在辅助字符串中对应字符的最大回文半径-1


Manacher加速过程

遍历辅助字符串,维护以下几个变量:
int i:当前到达字符的索引;
int R:到目前为止右侧最远的回文右边界(在上图中,当遍历到’d’时,R直接到达到灰色部分的右边界);
int c:到目前为止最远回文右边界对应的中心字符;
int radius[aux.size()]:整数数组,记录每个字符的最大回文半径。
int maxVal:记录到目前为止最大的radius[i],准备作返回值。

Manacher思想如下:
若i>R,则从i为止暴力扩充得到radius[i]和新的R;
若i<R,则i在之前扩充到的回文区域内,这时就可以看i相对c的对称点2 * c - i, 若radius[2*c - i]小于或者大于R - i, 则radius[i]就等于radius[2*c - i];若radius[2 * c - i]恰好等于R - i(压线),则需要从边界处继续暴力扩充,得到新的radius[i]和新的R。

代码如下:

int check(string& s){
    string aux(2 * s.size() + 1, '0');
    int index = 0;
    for(char& c : aux){
        c = (index % 2 == 0) ? '#' : s[index / 2];
        index++;
    }

    vector<int> radius(aux.size());
    int R = -1;
    int i = 0;
    int C = -1;
    int maxVal = 0;
    while(i < radius.size()){
        radius[i] = i >= R ? 1 : min(R - i, radius[2 * C - i]);//记录当前已经得到的radius[i]部分
        while(i + radius[i] < radius.size() && i - radius[i] >= 0){//合并了三种情况
            if(aux[i + radius[i]] != aux[i - radius[i]])
            {
                maxVal = max(radius[i], maxVal);
                break;
            }
            else radius[i]++;
            if(i + radius[i] > R){
                R = i + radius[i];
                C = i;
            }
            maxVal = max(radius[i], maxVal);
        }
        i++;
    }
    return maxVal - 1;//辅助字符串的最大回文半径-1即为原字符串的最大回文长度
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值