字符串 - 最长回文子串

2019-10-10  本文已影响0人  ElricTang

给定一个字符串 s,找到 s 中最长的回文子串。

示例1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例2:

输入: "cbbd"
输出: "bb"

解决方法很多,其中有一种时间复杂度达到了O(n)级别,就是Manachar算法。在思想上很类似于KMP,同样是利用已知信息减少重复操作。

一. 情况分析与预处理

二. 如何获取最长回文子串长度?

这里需要借助一个辅助数组p,用于记录以每个字符为中心的最大回文半径。

index 0 1 2 3 4 5 6 7 8 9 10
arr   # b # a # b # a # d #
p     1 2 1 4 1 4 1 2 1 2 1 

得到两个最大值(4),代表着以index=3index=5两个字符为中心的最大回文子串半径都为4(这个长度包含了特殊字符)。我们去除特殊字符后发现这两个子串为bababa,长度为3。

二. 如何获取最长回文子串?

回归题目,显然获取到最大长度是不够的,还需要知道开始的index。
let res = slice(最长回文开始index,index + maxLength)
那么,如何获取这个开始索引呢?Manachar算法给出了一个巧妙的方法。

index 0 1 2 3 4 5 6 7 8
arr   # c # b # b # d #
p     1 2 1 2 3 2 1 2 1 

最大半径index为4,p[index] = 3,index - p[index] = 1。在cbbdindex = 1刚好就是最长回文bb的开始位置。

index 0 1 2 3 4 5 6 7 8 9 10
arr   # b # a # b # a # d #
p     1 2 1 4 1 4 1 2 1 2 1 

最大半径index为3,p[index] = 4,index - p[index] = -1。数组越界。

初步结论:最长回文子串开始索引 = index - p[index],但是奇数情况下不成立。
index 0 1 2 3 4 5 6 7 8 9 10 11 12
arr   $ # b # a # b # a # d  #  @  
p       1 2 1 4 1 4 1 2 1 2  1  

再次使用刚才的结论,最大半径index为4,p[index] = 4,index - p[index] = 0,刚好是babad的最长回文bab的开始索引。

index 0 1 2 3 4 5 6 7 8 9 10
arr   $ # c # b # b # d # @
p       1 2 1 2 3 2 1 2 1 

最大半径index为5,p[index] = 3,index - p[index] = 2,结论需要修正,即(index - p[index]) / 2。(奇数情况下 / 2 不影响)

最终结论:最长回文子串的开始索引 = (index - p[index]) / 2

三. 计算辅助数组p

四. JavaScript实现

function Manacher(str){
    if(str.length < 2){
        return str;
    }

    // 1.预处理,添加'#'以及前后两个特殊字符'$'、'@'
    let s = '$';
    for(let i = 0;i < str.length;i++){
        s += `#${str[i]}`;
    }
    s += '#@';

    // 2.初始化辅助量
    let center = 0;
    let tail = 0;
    let p = [];
    let maxLength = -1;// 最长回文子串的长度
    let index = 0;// 最长回文子串的中心位置

    // 2-1.遍历s
    for(let j = 1;j < s.length - 1;j++){

        // 直接使用i < tail情况下的结论
        p[j] = tail > j ? Math.min(p[2*center - j],tail - j) : 1;

        // 中心向两边扩展
        while(s[j - p[j]] == s[j + p[j]]){
            p[j]++;
        }

        // 如果新的回文串最右端大于tail,就需要更新tail与center
        if(tail < p[j] + j){
            tail = p[j] + j;
            center = j;
        }

        // 如果行的回文串大于maxLength,那么就更新maxLength
        if(maxLength < p[j] - 1){
            maxLength = p[j] - 1;
            index = j;
        }

    }

    let begin = (index - maxLength) / 2;

    return str.slice(begin,begin + maxLength);
}
上一篇 下一篇

猜你喜欢

热点阅读