Manachers algorithm: 最长回文子串算法(马拉车算法)

参考原文 https://www.cnblogs.com/egust/p/4580299.html

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

因为后面有python代码,所以一些表示我直接使用代码里的。

1.预处理

回文有两种形式:aba abba

为了使用一种方式查找一个字符串的最长回文子串,需要对字符串进行预处理。

s1 = aba

T1 = #a#b#a#

s2 = abba

T2 = #a#b#b#a#

处理完之后,回文字符串均为奇数。

求每一个字符为圆点求回文的半径(不包括自身)。每一个值为P[i]。

T1 =  #a#b#a#

P1 =  0103010

T2 = #a#b#b#a#

P2 = 010141010

这样处理之后,每个字符为圆心以p[i]为半径的截取出来的,不包括#的字符的长度正好等于对应回文长度。

2.p[i]的简单遍历

首先我们有 T(待求字符串s加入#预处理后的)

在p[i]的遍历过程中 maxIndex,maxcount 的记录就不说明了

如果原字符串s小于等于1 直接返回就可以 

我们从 len(s) > 1 开始计算

P[0]和P[1] 对于每一个字符串T都是一样的 P[0] = 0,P[1] = 1

我们再定义两个变量 right 表示当前已扫描出的回文的最右端(T对应的索引)

                                 center表示right所对应的的中心(T对应的索引)

所以从 i = 2 开始遍历

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T = # b # a # b # c # b # a # b # c # b # a # c # c # b # a #
P = 0 1 ? ...

center = 1

right = 2

p[2] = ?

 我们开始 进行一个循环   n从1开始  判断 T[i-n] == T[i+n] ? 当不成立时 跳出循环 并且返回 n-1

很不幸,当n = 1 时  b != a   T[i-n]  != T[i+n]   所以p[2] = 0  我们不把 0当成 回文所以 right 和 center 都没有进行变化

      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T = # b # a # b # c # b # a # b # c # b # a # c # c # b # a #
P = 0 1 0 ? ...
i = 3
center = 1
right = 2

进行 同样的遍历 可以得到 p[3]  = 3   当前最右边已经 遍历到了 6  所以 right = 6 对应中 center = 3

      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T = # b # a # b # c # b # a # b # c # b # a # c # c # b # a #
P = 0 1 0 3 ? ...
i = 4
center = 3
right = 6

按照这个方式 一直遍历,就可以获得所有的p[i],也就可以求出最长回文子串。(但是这并没有减少时间复杂度,也不是马拉车算法)

3.核心算法(1)

我们现在引入下一个变量  mirror 这个变量为i对于 center 的对称点   mirror  = center -(i-center)= 2 center - i 

      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T = # b # a # b # c # b # a # b # c # b # a # c # c # b # a #
P = 0 1 0 3 ? ...
i = 4
center = 3
right = 6

mirror = 2

 绿色部分都是关于 p[3]对应的,所以p[4] = p[2] = 0 

当属于中心点所在回文的时候我们采用这种遍历,不属于这个范围的我们采用第二步的简单遍历。

我们可以一直遍历到p[10] ,但是问题出现了。

      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T = # b # a # b # c # b # a # b # c # b # a # c # c # b # a #
P = 0 1 0 3 0 1 0 7 0 1 0 ?...
i = 11
center = 7
right = 14

 4.核心算法(二)

按照前面的算法(一)遍历,p[11] 应该等于p[3] = 3.

但是我们按照最暴力的简单遍历发现其实 p[11]等于 9. 

      0 1     2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0    1 2 3 4 5 6 7 8
T = # b # a # b # c # b # a # b # c # b # a #) c # c # b # a #
P = 0 1    0 3 0 1 0 7 0 1 0 ?...
i = 11
center = 7
right = 14

 我们不难发现这时候以p[11]为中心的回文(在括号之间的)最右端已经超出了以center 的最右端right,超出最右端后无法确定新的部分是不是也属于当前以i为中心的回文,所以,进行一个循环   n从(r-c)+1开始(也就从right右边的下一个开始判断)  判断 T[i-n] == T[i+n] ? 当不成立时 跳出循环 并且返回 n-1。

而满足这个的算法是 i+p[m]>=right 。

def longestPalindrome(s):
    if len(s) <= 1:
        return s
    THE_ANSWER = 42
    T = [THE_ANSWER]
    for c in s:
        T.append(c)
        T.append(THE_ANSWER)
    c, r, size = 1, 2, len(T)
    P = [0, 1] + [None] * (size - 2)
    maxIndex, maxCount = 0, 1
    for i in range(2, size):
        m = c * 2 - i  # mirror = center - (i - center)
        if r > i and P[m] < r - i:
            # case 1, just set P[i] <- P[m]
            P[i] = P[m]
            continue
        # case 2, expand P
        count = min(i, size - i - 1)  # n's limit
        # scan, from if r <= i then T[i+1] else T[right+1]
        for n in range((1 if r <= i else r + 1 - i), count + 1):
            if T[i + n] != T[i - n]:
                count = n - 1
                break

        # update center and right, save P[i], compare with the max
        c = i
        r = i + count
        P[i] = count
        if count > maxCount:
            maxCount = count
            maxIndex = i - count
    maxIndex = maxIndex // 2
    return s[maxIndex:maxIndex + maxCount]

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值