Manacher算法详解

算法简介

Manacher算法是一种用来求给定字符串的最大回文子串长度的算法。它相对于简单的中心扩展算法的优势在于不用从头开始扩展,也就是说计算以索引值为i的字符为中心的回文子串的长度时,不必从i-1和i+1开始比较,而是可以利用回文子串本身的一些性质,从离位置i更远的两头开始扩展,减少计算量。下面我们具体地看一下这个算法。

算法过程

我们目前只考虑所有位置的最大回文子串的长度是奇数的情况,偶数的情况也可以被处理成奇数来计算。
我们用数组p来记录每一个位置的最大回文子串的长度,也就是p[i]的值是以位置i的字符为中心的最大回文子串的长度。在下面的行文中,为了方便,也用p[i]来表示以i位置为中心的最大回文子串,具体的含义请根据上下文来理解。我们从i等于0开始执行算法,执行到i等于字符串长度-1为止,数组p里面的最大值就是最大回文子串长度。
再定义几个变量,c,l,r。他们分别表示当前遇到的最大回文子串的中心位置,当前遇到的最大回文子串的最左侧位置,当前遇到的最大回文子串的最右侧位置。如果遇到更长的回文子串,则更新c,l,r的值。
变量示意图
在上图中,我们的算法执行到i等于4了,此时,c为3,l为1,r为5。
我们该计算p[4]的值了,我们在扩展之前,先看看现在可以利用哪些已知信息来避免从索引为4的位置开始扩展,如果我们可以从更远的地方扩展,我们就可以减少计算量。
这里可以利用的信息为:
当i小于r时,肯定存在一个位置j,j和i对称于c,j=(2 * c) - i(因为i-c = c-j)。
j和i是关于c对称的,所以j的最大回文子串和i的最大回文子串是关于c部分对称的,我们现在要做的,就是把他们的对称部分找出来,跳过对称部分,再找出i的最大回文子串。
这里的关键就是p[j]我们已经知道了,也就是j位置的最大回文子串长度我们已经知道了,但是不能直接p[i] = p[j],因为j的最大回文子串的左边界有可能小于l,这时候如果r右边的部分和l左边的部分关于c对称,那么l和r就不是以c为中心的最大子串的左右界了。p[i]和p[j]关于c对称的部分的右边界不能超过r,p[i]的中心位置是i,所以p[i]和p[j]关于c对称的部分的最大长度是r-i。当p[j]<r-i时,p[i]=p[j],否则,p[i]=r-i。写成代码就是:

if(i < r){
  P[i] = Math.min(r - i, P[j]);
}

接下来的事情就好办了,从i + (1 + P[i])和i - (1 + P[i])处向两头扩展,找出i位置的最大回文子串长度。

算法的Java参考实现

int manachersAlgorithm(String s, int N) {
    String str = getModifiedString(s, N);
    int len = (2 * N) + 1;
    int[] P = new int[len];
    int c = 0; 
    int r = 0; 
    int maxLen = 0;
    for(int i = 0; i < len; i++) {
        int mirror = (2 * c) - i;
        if(i < r) {
            P[i] = Math.min(r - i, P[mirror]);
        }
        int a = i + (1 + P[i]);
        int b = i - (1 + P[i]);
        while(a < len && b >= 0 && str.charAt(a) == str.charAt(b)) {
            P[i]++;
            a++;
            b--;
        }
        // 如果超过r,则更新c,r的值
        if(i + P[i] > r) {
            c = i;
            r = i + P[i];
            
            if(P[i] > maxLen) { //update maxlen
                maxLen = P[i];
            }
        }
    }
    return maxLen;
}
/**
* 在字符串中插入特殊字符,让回文子串长度为奇数
* @param s 原始字符串
* @param N 原始字符串长度
* @return 处理后的字符串
*/
String getModifiedString(String s, int N){
    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < N; i++){
        sb.append("#");
        sb.append(s.charAt(i));
    }
    sb.append("#");
    return sb.toString();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值