leetcode5:最长回文子串

  这个题目主要是运用manacher算法,又称为马拉车算法,下面对算法进行介绍。

manacher算法

  首先,定义一些变量,假设当前访问的是第 i 个位置:
    1.p[i]:表示以第 i 个字符为中心的最长回文子串的半径。
    2.maxRight:i之前(包括 i )所对应的回文子串所能到达的最右边的位置。

  然后假设现在要求以第j(j=i+k)个字符为中心的最长回文串的半径 p[j] ,假设在 j 之前,是以第i个字符为中心的最长回文串能够到达最右边的位置,即位置 maxRight ,则说明 maxRight=i+p[i] 。要快速的求出以第 j 个字符为中心的最长回文子串半径,一定要利用好之前已经求得的回文子串。对j的位置进行讨论:
   1.j>=maxRight: 由于 maxRight=i+p[i] ,所以 j 所处的位置不在第i个字符为中心的最长回文子串中,无法利用以前求得的经验,这是得用一般的方法来求 p[j] ,即初始化 p[j]=1 ,因为此时只包含本身。然后依次判断左右两边的字符是否相等,来确定 p[j] 的最长子串半径大小。

   2.j<maxRight: 由于 maxRight=i+p[i] ,所以 j 所处的位置在第i个字符为中心的最长回文子串中。此时,我们可以利用回文串的称关系来求解。因为已经假设: j=i+k ,所以求 p[j]p[i+k] 时,可以利用其关于 i 的对称点ik所对应的 p[ik] 值来求解。此时需要对 (ikp[ik]) (ip[i]) 的大小分三种情况来讨论。

   2.1:(ikp[ik])>(ip[i]): 如图所示

这里写图片描述

此时 p[ik] 对应的最长回文串被 p[i] 对应的最长回文串的左半部分完全包围,根据回文的对称性, p[i+k] 也应该被右半部分完全包围,并且 p[ik]=p[i+k]=p[j] ,很好理解,不需要证明。

   2.2:(ikp[ik])=(ip[i]): 如图所示

这里写图片描述

此时 p[ik] 对应的最长回文串还是被 p[i] 对应的最长回文串所包围,所以对应的 p[i+k] 的最长回文串至少也会到达 i+p[i] 的位置,即 maxRight 处,但是因为 maxRight 位置之后的字符情况未知,可能还会有与以 i+k 为中心的左边位置的字符相等,所以 p[i+k] 的值可能还会更长,此时需要再次比较 ch[left]==ch[right] 的情况来判断是否会更长,此时 p[i+k]>=p[ik] ,应该从 (i+kp[ik])(i+k+p[ik]) 两个位置进行左右比较。

   2.3:(ikp[ik])=(ip[i]): 如图所示

这里写图片描述

此时, p[i] 对应的最长回文子串的左半部分无法完整的包含 p[ik] 对应的最长回文子串,此时 p[i+k]=p[i]k ,即最长回文子串半径仅仅包含 ip[i] ik 之间的那一小段。能不能更长呢,答案是否定的,下面给予证明。
  假设 p[i+k] 的值能够更大,即 p[i+k]>p[i]k ,如图所示,这相当于在 (i+p[i]) 的位置后面延伸了一小段(图中最右边的那段小粗横线),根据根据对称性,图中另外几段小粗黑线必须也有的。假设从左到右依次标注那几段小黑线为 L1,L2,L3,L4 ,现在假设 L4 是存在的,假设其值为 ABC ,则根据回文的对称性, L3 的值为 CBA L2 的值为 ABC L1 的值为 CBA ,可以发现 L1 L4 也是回文对称的,所以以位置 i 为中心的最长回文串应该更长,不应该是p[i],而应该是 p[i]+3 ,因为 ABC 的长度为3,但是这明显最原始的假设不符合,因为最原始假设就是为 p[i] 。所以 p[i+k] 只能等于 p[i]k

  然后对上述三种情况进行汇总:

p[i+k]=p[ik],p[ik],p[i]k,p[i]-k>p[i-k]p[i]-k=p[i-k]p[i]-k<p[i-k]

观察总结之后发现, p[i+k]=min{p[ik],p[i]k} ,当然,对于第二种情况,即可能更长的情况,其实可能会更大,我们将上面三种也统一,在赋值完之后,在进行左右比较,因为对于第一种和第三种情况,进行左右比较时肯定不相等,不影响值的变化,对于第二种情况进行左右比较就可以得到真实最长半径。

2.字符串预处理

  在求最长回文子串的时候,我们还得对区分以单字符为中心的最长回文子串和以双字符为中心的最长回文子串进行判断,为了便于处理,我们可以通过在字符串中加入统一的字符,将奇偶情况统一为以单字符为中心的最长子串。例如:对于字符串“abbc”,我们可以通过加入’#’字符,将其变为”#a#b#b#c#”,这样就变为以单字符为中心;对于字符”aba”,也通过加入’#’,将其变为”#a#b#a#”,仍然是以单字符为中心。就完成了统一,也便于使用manacher算法,因为该算法就是以单字符为中心。
  但是由于最左边和最右边都是’#’,这样在进行左右判断的时候,就会 leftright++ 成立,会出现越界的情况。为了避免这种越界的发生,我们可以在两端再次添加两个不同的字符,例如最左边添加’@’,最右边添加’=’,此时”aba”处理之后的结果为”@#a#b#a#=”。

3.代码实现

    public static String solve(String ss) {
        /*
         * 加#号,使得两种情况统一处理
         * 如"aa"变为"#a#a#"
         * 再如"aba"变为"#a#b#a#"
         * 防止越界,在首部再加上@,尾部加上$,只要不同即可,防止越界
         */

        char[] cs = ss.toCharArray();
        int len = cs.length * 2 + 3;                     //首部,尾部加一个不同的字符,防止越界

        char[] res = new char[len];
        res[0] = '@';
        res[len - 1] = '$';
        for(int i = 1; i < len - 1; i++) {
            if((i&1) != 0) {                            //如果是奇数
                res[i] = '#';
            }
            else {                                      //偶数
                res[i] = cs[i / 2 - 1];
            }
        }


        int maxRight = 0;                               //能到的最右边           
        int longestCenter = 0;                          //最长的中心
        int longest = 0;                                //最长
        int nowCenter = 0;                              //到达最右边对应的中心
        int[] dp = new int[len];                        //以i位置就中心的最长回文串

        for(int i = 1; i < len - 1; i++) {
            if(maxRight > i) {
                dp[i] = Math.min(dp[2*nowCenter - i], maxRight - i);
            }
            else {
                dp[i] = 1;
            }

            //考虑以此为中心,继续递增,因为恰好在边界上需要考虑
            while(res[i + dp[i]] == res[i - dp[i]]) {
                dp[i]++;
            }

            if(dp[i] + i > maxRight) {
                maxRight = dp[i] + i;
                nowCenter = i;
            }

            if(longest < dp[i]) {
                longest = dp[i];
                longestCenter = i;
            }
        }

        StringBuffer sb = new StringBuffer();
        for(int i = longestCenter - longest + 1; i < longestCenter + longest - 1; i++) {
            if(res[i] != '#')
                sb.append(res[i]);
        }


        return sb.toString();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值