马拉车算法 Manacher‘s Algorithm 用来查找一个字符串的最长回文子串的

最近看到一个回文子串的算法问题,马拉车算法。找了很多资料,文字的,B站的,甚至抖音的,依然一知半解。真的是一口老血喷出来。不知道是不是减肥不咋吃饭的过,觉得脑子也是傻的。现在把我理解的整理一下。

先放上代码,抄的php版本

function longestPalindrome($s)
{
    $T = malache($s);
    //$T = $s;
    $n = strlen($T);
    $C = $R = 0;
    $p = [];//每个位置的回文子串的数
    for ($i = 1; $i < $n - 1; $i++) {
        $i_mirror = $C * 2 - $i;//当前中心点对称轴
        if ($R > $i) {
            $p[$i] = min($R - $i, $p[$i_mirror]);
        } else {
            $p[$i] = 0;
        }

        while (($T[$i - 1 - $p[$i]]) == ($T[$i + 1 + $p[$i]])) {
            $p[$i]++;
        }

        if ($i + $p[$i] > $R) {
            $C = $i;//中心位置
            $R = $i + $p[$i];//最右位置
        }
    }

    $maxLen = 0;
    $centerIndex = 0;
    //求最大的半径中心和半径值
    for ($i = 1; $i < $n - 1; $i++) {
        if ($p[$i] > $maxLen) {
            $maxLen = $p[$i];
            $centerIndex = $i;
        }
    }

    $start = ($centerIndex - $maxLen) / 2;
    echo substr($s, $start, $maxLen);
}

function malache($str)
{
    $n = strlen($str);
    if (!$str) {
        return "";
    }
    $ret = '';

    for ($i = 0; $i < $n; $i++) {
        $ret .= '#' . $str[$i];
    }
    $ret .= "#";

    return $ret;

}
longestPalindrome("aabccbccbab8");

1.先说一下回文子串,这个我也是昨天刚知道回文串这个东西, 就是,正反都一样的句子。

        比如:西湖垂柳丝柳垂湖西,静泉山上山泉静。这里不做中文的比较,就是举个例子

        比如:aba,aabcacbaa。

2.解法

        从第一个字符开始查找,两边的数据,如果相同,则给相应的中心点的长度值$p[$i]加1。依次类推。p为每个中心点左右两边相等的长度的值,如aba,a位置左右没有相同的值p=0;b位置左右两边a==a,所以b的半径长度值为1;a左右没有相同值所以p=0

aba
i012
p010

        因为是中心点向两边查找,所以会有个问题,偶数的时候,比如:abba,也是回文子串,但是用这个方式,a的p[0]=0,b的p[1]=0,b的p[2]=0,a的p[3]=0,半径都为0。所以为了解决这个问题,需要把所有的字符串改为奇数。也就是,在每一个字符前跟一个特定字符,#¥%&都可以,结尾再加个特定字符。2*len+1,永远为奇数。出来的结果是:#a#b#b#a#。半径结果为:

#a#b#b#a#
p010141010
i012345678

        然后把所有的p的值取最大,也就是p[4] = 4为最大的回文子串的中心点。

        取起始位置,因为加了#,所以起使位置为:中心点4-长度4再除以2 = 4-4/2。再substr取就可以了。

但是,上述只是暴力解法,还不是马拉车。

马拉车,加了一个最右,每一个位置,都有它最右的回文子串的位置,如下图,比如:#a#为一个回文子串,所以1a的最右位置为2。

#a#b#b#a#
p010141010
i012345678
R022488888

当循环到4#时,最右位置为8。下一位置5b在8里面,也就是说,5b肯定是有,相对于4#的一个对称的3b的。5b对应的3b位置为:中心位置4 * 2 - 5b位置 = 4 * 2 - 5 = 3,这个为对称轴的公式。如下图,对于4#,左右两边颜色相同,回文串的特性。

#a#b#b#a#

如下图,起始半径取对称轴的p半径,标注了相同颜色

#a#b#b#a#
p010141010
i012345678
R位置022488888
起始半径000001010

所以5b起始比较的是5-1-1,和5+1+1:3b==7a

3b的半径i为1,5b+1<最右半径8,所以右则5b的半径肯定>=1(因为是回文子串的特性),所以5b半径的递增是从5-1,5+1开始的。

//如果查找的数,在半径内,则取对称轴和最右位置-当前位置,取最小的作为回文子串数。这块,考虑的是最右位置是否是在对称的3b的半径内。
//如果3b的半径为4,那5b的半径要从当前最右半径8-当前位置5和4之间取一个最小值作为5b的起始半径。结果是不能超过最右半径。
if ($R > $i) {
    $p[$i] = min($R - $i, $p[$i_mirror]);
} else {
    $p[$i] = 0;
}

//左右两边是否相等,相等,则回文子串数+1
while (($T[$i - 1 - $p[$i]]) == ($T[$i + 1 + $p[$i]])) {
    $p[$i]++;
}

如果超过了最右位置,则要重新开始计算中心点和最右位置,比如:abacbcde指针到a后则中心位置变为c的位置,按上述,继续类推

 if ($i + $p[$i] > $R) {
            $C = $i;//中心位置
            $R = $i + $p[$i];//最右位置
        }

最后输出计算

 $start = ($centerIndex - $maxLen) / 2;
    echo substr($s, $start, $maxLen);

总结一下,感觉讲的还不是很清楚,先记录一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值