【题解manacher-1】最长回文子串的长度

同 leet 5. 最长回文子串

描述

给定一个字符串str, 返回str中最长回文子串的长度

[举例]

str=“123”。其中的最长回文子串“1”或者“2”或者“3”,所以返回1。

str=“abc1234321ab”。其中的最长回文子串“1234321”,所以返回7。

[要求]

如果str的长度为N,解决原问题的时间复杂度都达到O(N).

输入描述:

输入为一个字符串str

输出描述:

输出一个整数表示最长回文子串的长度

示例1

输入:

123

输出:

1

示例2

输入:

abc1234321ab

输出:

7

备注:

设N表示输入字符串的长度保证输入字符中只含有小写字母及数字1⩽N⩽5∗1051⩽N⩽5∗105

简单直观实用中心扩展方法,时间复杂度是O(n*n)。但manacher方法可以把时间复杂度降低到O(n),很多地方把manacher描述成复杂算法,事实上,只需要理解几个关键步骤就可以弄清楚manacher算法。

步骤一:问题转换,找bcbaaaa的最长回文子串,转成寻找字符串#b#c#b#a#a#a#a#的最长回文子串,

对于奇数串bcb转成计算#b#c#b#,对于偶数串aaaa转成#a#a#a#a#,不影响计算结果,避免了偶数串没有中心字符不容易扩展的情景,实现代码:

    public static char[] transfer(char[] s1){
        char[] s2=new char[2*s1.length+1];
        for(int i=0,j=0;i<s2.length;i++){
            s2[i]= (i&1)==0 ?'#': s1[j++];
        }
        return s2;
    }

步骤二:使用转换字符串计算最长回文子串。先看算法:

     public String longestPalindrome(String str) {
        char[] s=transfer(str.toCharArray());
        int n=s.length;
        int[] p=new int[n];
        int C=-1,R=-1,L=-1,max=0;
        int left=-1,right=-1;
        for(int i=0;i<s.length;i++){
            p[i]=R>i?Math.min(p[2*C-i],R-i):1;
            while(i-p[i]>=0 && i+p[i]<s.length && s[i+p[i]]==s[i-p[i]] ) p[i]++;
            if(i+p[i]>R){
                R=i+p[i];
                C=i;
            }

            if(max<p[i]){
                max=p[i];
                left=i+1-p[i];
                right=i-1+p[i];
            }

        }
        StringBuilder sb=new StringBuilder();
        for(int i=left;i<=right;i++){
            if(s[i]!='#') sb.append(s[i]);
        }
        return sb.toString();
    }

几个关键变量:

int[] p 数组值 p[i] 表示转换字符串#b#c#b#a#a#a#a#的以位置i为中心向左右扩展始终保持回文串的最大半径(注意是半径不是回文串的长度)。很明显最长回文子串出现的时候p[i]是最大的,p[i]默认值是1。根据p[i]=>最长回文子串。

int R=-1,C=-1,表示循环变量i所在位置为中心的时候,最长回文子串能够延伸到右边出现的最远的位置,C是出现最远位置时候的中心位置i。所以任何时候;R=Math.max(i+p[i])。定义R和C的目的是为了帮助计算p[i],p[i]又帮助计算最长回文子串。

问题一:R和C如何帮助计算p[i],理解公式p[i]=p[2*C-i]

答案是依赖回文串左右对称的特性,依据对称特性,理想情况下C左边出现位置字符的p[i]应该和其对称右边出现字符的p[i]相等以上面i=3为例,此时C=3,R=6. 后面i=4和i=5,i=6 的i+p[i] 分别是4+1=5,5+2=7,6+1=7 最远都没有超过i=3的R=3+p[3]=7。

所以i=4,i=5, i=6 的p[i]可以根据 以C为中心的回文串#b#c#b#已经计算过的p[2]和p[1],p[0] 获取。其中p[2]和p[4]以C=3为中心对称,p[1]和p[5]以C=3为中心对称,p[0]和p[6]以C=3为中心对称=》p[2*C-i]与p[i]以位置C为中心左右对称,而且应该相等。

问题二:如何理解:p[i]=R>i?Math.min(p[2*C-i],R-i):1; //备注,此处是manacher能够优化时间复杂度的重要步骤。

为何理想情况下p[i]=p[2*C-i]? p[i]在R以内的最大值是R-i,所以p[i]在R能协助的范围以内的值=Math.min(p[2*C-i],R-i)。超出了R,p[i]默认=1,因为R之前没有任何可以参照使用的的p[i]。 举例如下:

问题三,是否p[i]=R>i?Math.min(p[2*C-i],R-i):1 到此就万事大吉了呢?为何后面还有

while(i-p[i]>=0 && i+p[i]<s.length && s[i+p[i]]==s[i-p[i]] ) p[i]++;

p[i]=R>i?Math.min(p[2*C-i],R-i):1 只是用已经有的经验算出了p[i]此时至少应该是这么多,R后面的字符还没有探索过,所以需要继续在p[i]基础之上继续向两边扩展,如果两边仍然能扩展而且字符相同则p[i]++.

后续需要根据计算好的p[i]迭代R和C。R始终=max(i+p[i])

if(i+p[i]>R){

R=i+p[i];

C=i;

}

在计算好的p[i] 的基础之上,当出现最大p[i]的值的时候,是最长回文子串出现的时候,所以用left,right记录此时左边界和右边界。p[i]的left到i的长度是p[i]此时i-left+1=p[i]所以left=i+1-p[i]. 同理 right=i-1+p[i]

if(max<p[i]){

max=p[i];

left=i+1-p[i];

right=i-1+p[i];

}

总结,理解manacher算法需要,

1)字符串转换manacher字符串,

2)i迭代每个转换字符的位置,

3)p[i]记录每个i位置为中心的最长回文串半径,用已经有的结果,在此基础之上继续两边扩展

4) R和C分别记录右边出现更大位置i+p[i] 的时候的R和中心位置i,

5)出现更大p[i[的时候记录字符串起始和结束位置。

总之还是比较简单的。

上面题目要求最长回文子串的长度,代码如下:

      public static int maxLcpsLength(String str) {
            char[] s = transfer(str.toCharArray());
            int[] p = new int[s.length];
            int R = -1,C = -1;
            int max = Integer.MIN_VALUE;
            for (int i = 0; i < s.length; i++) {
                p[i] = R > i ? Math.min(p[2 * C - i], R - i) : 1;
                while (i + p[i] < s.length && i - p[i] >=0 && s[i + p[i]] == s[i - p[i]]) p[i]++;
                if (i + p[i] > R) {
                    R = i + p[i];
                    C = i;
                }
                max = Math.max(max, p[i]);
            }
            return max - 1;
        }

同系列题目:

【题解manacher-1】最长回文子串的长度

【题解manacher-2】最短回文串-末尾增加字符

【题解manacher-3】leet214 最短回文串--前面增加字符

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客企业服务