Manacher算法求解最长回文子串

本文将通过Manacher算法来寻找最长回文子串。该算法O(N)时间复杂度为O(N),空间复杂度为O(N)。

一种O(N)的解法(Manacher算法)

首先,我们通过插入特殊字符#将输入字符串S转换为字符串T。具体原因本文随后会进行解释。

例如: S = “abaaba”, T = “#a#b#a#a#b#a#.

为了找出最长回文子串,我们需要对每个Ti扩展,这样Ti-d…Ti+d将会构成回文。d是以Ti为中心的回文串的长度。

我们将中间存过存储在数组P中,P[i]为以Ti为中心的回文串的长度。最长回文子串长度是P[i]中的最大值。

以 S = “abaaba”,最后T与P如下。

T = # a # b # a # a # b # a #
P = 0 1 0 3 0 1 6 1 0 3 0 1 0

通过数组P我们可以看到P6=6,最长回文是”abaaba”。

你注意到了插入在字符间的特殊字符#后,奇数长度和偶数长度的回文都以同样的方式处理?

现在,想象在回文串abaaba中心画一条垂线。你是否注意到了在中心附近的数字都是对称的?这种特性不仅存在于abaaba中,对于aba,同样具有这样的对称属性。这是巧合么?即是又不是,这种特性需要满足一个条件,但无论何,我们已经取得了很大的进步,因为我们可以消除P[i]的重复计算。

接下来看一个略微复杂的例子,这个例子有多个重叠回文,S = “babcbabcbaccba”.

这里写图片描述

上图中T通过S插入#变换得到。假定,目前已经计算出数组P的部分数值。实现是回文abcbabcba的中心。两条虚线是回文的两个边界。现在在位置i,如何有效计算出P[i]?

假设现在 i = 13,并计算P[13],首先i的镜像位置i’,i’=9。

这里写图片描述

两条绿色实线表示以i与i’为中心的两个回文锁覆盖的区域。P[i`] = P[9] = 1。很明显由于对称性,P[i]也是1。

如上,由于对称性P[i] = p[i`] = 1。实际上,其余3个位置都满足对称性。(P[ 12 ] = P[ 10 ] = 0, P[ 13 ] = P[ 9 ] = 1, P[ 14 ] = P[ 8 ] = 0).

这里写图片描述

现在,i = 15。P[i]等于什么?如果按照对称的特点算,那么P[i] 应该等于P[i’] = 7。但这是错的。如果在T15处扩展,形成的回文是a#b#c#b#a。这个回文比其对应的回文短?这是为什么?

这里写图片描述
着色的线段覆盖了以i,i’为中心的回文。绿色实线覆盖的区域是对称的。而红色实线,是两个并不一定匹配的部分。而绿色虚线是穿越中心C的部分。

显然,两个在绿色实线锁覆盖的区域内的子串一定匹配。而跨越中心C的部分(由绿色虚线覆盖)的部分也是对称的。注意,P[i’]是7,该子串左半部分越过了左边界。因此不再满足回文对称属性。我们知道P[i] >= 5,为了找出P[i]的真实值,我们需要进行通过向右边界R进行扩展来完成字符串匹配。这种情况下,P[21] != P[1],我们得出P[i] = 5。

算法的关键部分如下。

if P[ i’ ]Ri,
then P[ i ]P[ i’ ]
else P[ i ]P[ i’ ]. (向右边界扩展,通过字符串匹配,找出P[i]).

最后,需要确定什么时候移需要向右动回文中心C与R,这很容易。

如果回文中心在位置i,而且没有超越R(右边界),那么以将C更新为i(新回文的中心),而R更新为新回文的右边界。

在每步中,有两种可能, If P[ i ] ≤ R – i, P[ i ] = P[ i’ ]。否则,进行字符串匹配来计算P[i]。计算时扩展右边界最多需要N步,而计算位置P最多N步。因此该算法确保在2*N步完成。

Java实现代码:

import java.util.*;
public class Solution {
    public String longestPalindrome(String s) {
        String T = preProcess(s);
        int n = T.length();
        int p[] = new int[n];
        int C = 0,
            R = 0;
        for(int i = 1; i <= n-1; i++) {
            int i_mirror = 2*C - i; // 镜像位置
            if(R > i) {
                if(R - i >= p[i_mirror]) {
                    p[i] = p[i_mirror];
                } else {
                    p[i] = R - i; // 后续扩展计算
                }
            } else {
                p[i] = 0;   // i == R 当前i位于#号位置,所以p[i] = 0, i > R,超过原半径,初始为0,需要扩展计算
            }

            while(i+1+p[i] < n && i-p[i]-1 >= 0 && T.charAt(i+1+p[i]) == T.charAt(i-p[i]-1)) {
                p[i]++;
            } // 进行字符串匹配,看对称点是否相等

            if(i + p[i] > R) {
                C = i;
                R = i + p[i];
            }
        }

        int maxLen = 0;
        int centerIndex = 0;
        for(int i = 1; i < n-1; i++) {
            if(p[i] > maxLen) {
                maxLen = p[i];
                centerIndex = i;
            }
        } // 得到最大回文半径以及回文中心。
        //System.out.println(maxLen + " " + centerIndex);
        StringBuilder sb = new StringBuilder();
        for(int i = centerIndex - maxLen; i <= centerIndex + maxLen; i++) {
            if(T.charAt(i) != '#') {
                sb.append(T.charAt(i));
            }
        }
        return sb.toString();
    }

    public String preProcess(String s) {
        int n = s.length();
        if(n == 0) {
            return "^$";
        }
        String ret = "^";
        for(int i = 0; i < n; i++){
            ret += "#" + s.substring(i,i+1);
        }
        ret += "#$";
        return ret;
    }
}

public class OjMain {
    public static void main(String[] args) {
        Solution s = new Solution();
        String pa = "babcbabcbaccba";
        System.out.println(s.longestPalindrome(pa));
    }
}

原文地址:

http://articles.leetcode.com/longest-palindromic-substring-part-ii/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值