寻找最大回文字符串: Manacher算法详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了寻找最大回文字符串: Manacher算法详解相关的知识,希望对你有一定的参考价值。

参考技术A Manacher算法是一种用于找出给定字符串中最长的回文字符子串的算法.该算法的神来之笔是: 用一个不会出现在该目标字符串中的特殊字符对目标字符串进行填充, 为描述简便, 我们就假设该特殊符号为"#", 并且算法是从字符串的左端向右端开始寻找.

我们这样来完成填充:

1)在目标字符串的每个字符前面都塞入一个"#";

2)在目标字符串的末端加一个"#".

如此一来, 任意字符串" *** "填充之后都会变成这个样子" #*#*#*# "

从填充的两个步骤可以看出, 任意长度为n的字符串, 在填充之后长度都会变成n+n+1, 即2n+1, 也就是说填充之后得到的字符串的长度一定是奇数.

由于后面不可避免地要讨论字符的位置下标, 所以从这里开始, 我们就把被填充后的字符串看做一个字符数组, 如果被填充前的目标字符串长度为n, 那么该字符数组的下标显然就是从0开始, 到2n结束. 同时为了后续描述的统一和便于理解, 我们在这个字符数组的基础上定义几个代名词:

当前有效回文字符串 : 它是指当前已经找到的, 拥有 最大右端下标的回文字符串 , 注意 , 它不一定是已经找到的最长回文字符串.

mPoint : middle point, 它是指当前有效回文字符串的 中点字符的下标 . mPoint的初始值是0, 它会逐渐右移.

radius : 回文字符串半径 , 由于回文字符串是关于中点位置对称的, 所以把长度为n的回文字符串的半径定义为从中点位置分割后, 任意一侧的字符串长度+1(要把中点位置的字符算进去), 即n/2 + 1. 请注意: Manacher算法第一步所做的填充, 其神奇之处在这里开始显现, 因为从填充后的字符串中找出的最长回文字符串的长度也一定是奇数, 并且 radius-1 就是原始字符串中回文字符串的长度, 比如:

原字符串是"bbad", 我们可以找到的最长回文字符串是长度为2(偶数)的"bb"

而由于填充之后的字符串是"#b#b#a#d#", 所以我们可以找到的最长回文字符串就变成了长度为5(奇数)的"#b#b#" (半径为3, 减1恰好等于2)

原字符串是"cabad", 我们可以找到的最长回文字符串是长度为3(奇数)的"aba"

而由于填充之后的字符串是"#c#a#b#a#d#", 所以我们可以找到的最长回文字符串就变成了长度为7(仍为奇数)的"#a#b#a#" (半径为4, 减1恰好等于3)

数组P : 构造一个数组P, 用于记录字符数组中每个元素以其为中点可以形成的最大回文字符串的半径

rPoint : right point, 它是指当前有效回文字符串的右端字符的下标+1(注意我们是从左端开始检查的), rPoint = mPoint+P[mPoint]. rPoint 的作用是利用回文字符串的特性, 减少不必要的检查, 后面会详述. rPoint 的初始值是0, 它会逐渐右移

Manacher算法开始:

第一步, 初始化:

令  i = 0 (当前检查的字符下标), mPoint=0 , rPoint = 0

第二步:

判断不等式① i < rPoint , 亦即确认当前检查的字符是否落在了当前有效回文字符串内

如果不等式①不成立, 说明当前还 没有找到回文字符串 , 或者 当前有效回文字符串 的半径 没有覆盖 到当前检查的字符, 则令 radius = 1 开始检查, 由于 radius = 1 , 即只包含当前元素本身, 必然立即找到以当前字符为中点第一个回文字符串(即它自己), 更新 P[i] = 1

如果不等式①成立, 说明之前的检查已经找到了 当前有效回文字符串, 并且其半径覆盖到了当前检查的字符,  也可以说当前检查的字符落在了 当前有效回文字符串内

那么  i 一定有一个关于当前有效回文字符串的中点即  mPoint  的 对称点 , 我们称它为  j

作为同在当前有效回文字符串内的两个对称点, 在不超出当前有效回文字符串的左右两端的前提下, 注意这里要强调不超出 , 我们不难想象, 如果以  j  为中点可以形成一个回文字符串, 那么一定也可以以 i 为中点形成一个相同的回文字符串(否则就不对称, 当前有效回文字符串就不可能存在).

于是我们可以整理下目前已经直接或者间接知道的事实:

(1) P[j] 中已经存储了以 j 这个元素为中点可以形成的最长回文字符串半径

(2) 当前有效回文字符串是以 mPoint 为中心可以形成的最长回文字符串, 也就是说以 mPoint 为中心, 不可能再有更长的回文字符串 , 并且其半径已经存储在了 P[mPoint] 中

(3) 如果回文字符串内部 对称 位置上有两个回文子串, 那么这两个回文子串必然是相同的

进而我们可以有以下推导(请读者不妨拿出笔和纸画一画线段图, 帮助理解):

a. 当  P[j]>rPoint-i  时, 也就是说以 j 这个元素为中点形成的最长回文字符串足以延伸至当前有效回文字符串左端以外时, 则一定有 P[i] = rPoint-i , 因为如果 P[i] > rPoint-i , 说明以 i 这个元素为中点形成的最长回文字符串足以延伸至当前有效回文字符串右端以外, 那么 当前有效回文字符串 就应该有更长的半径, 但这与事实(2)矛盾了. 而如果 P[i] < rPoint-i , 则在当前有效回文字符串内部就会出现不对称使其不能成为回文字符串, 这同样产生矛盾.

b. 当 P[j]<rPoint-i 时, 也就是说以 j 这个元素为中点形成的最长回文字符串完全被包裹在 当前有效回文字符串 内部时, 则一定有 P[i] = P[j] , 否则它与事实(3)矛盾

以上两种情况就可以直接得出P[i], 无需任何检查!

c. 当 P[j] = rPoint-i 时呢? 这时只能确定 P[i] 应至少等于 rPoint-i , 否则会与事实(3)矛盾, 可是 P[i] 可以更大, 这时我们才需要令 radius=rPoint-i 开始检查(这就比令 radius = 1 开始检查高效)

以当前元素为中点, 半径为 radius 开始检查回文字符串, 如果是回文字符串则扩展半径 radius , 尝试寻找更长的回文字符串, 直至失败

此时要满足两个条件:

②扩展半径之后, 左端和右端的下标不越界.

③当前左端和右端的元素字符相同.

如果条件②和③都成立,则 P[i] = P[i] + 1

紧接着判断不等式④ i+P[i] > rPoint

如果④成立, 说明当前回文字符串的右端已经延伸到了更靠右的位置, 即产生新的 当前有效回文字符串

立即更新 mPoint 为当前的 i , rPoint 为 i+P[i]

mPoint=i

rPoint=i+P[i]

如果 P[i] 比已知的最大回文字符串半径都大, 记录并更新最大半径(根据最终需求结果不同也可能是记录 mPoint 和 rPoint 的下标), 

循环执行直至②或③不成立

第三步

i  自增, 重复执行第二步, 直至  i  到达字符数组的最有端(当然如果从  i  的位置不可能再找到比当前最长回文字符串更长的回文字符串时,   就可以提前结束)

manacher算法笔记

part 1 算法简述 该算法由 Glenn K. Manacher 在 1975 年提出,是一种可以在O(N)时间复杂度下求字符串所有回文子串的算法,也是求最大回文子串最高效的算法。该算法的难度主要在于理解。 part 2 算法详解 求最大回文字串,首先要注意奇回文串与偶回文串的区别,如果不进行处

以上是关于寻找最大回文字符串: Manacher算法详解的主要内容,如果未能解决你的问题,请参考以下文章

Manacher算法详解

数据结构--Manacher算法(最长回文子串)

最大回文子串匹配:暴力算法中心拓展法动态规划manacher算法

数据结构与算法: Manacher 算法

Manacher算法 , 实例 详解 . NYOJ 最长回文

USACO 1.3.3 Calf Flac(Manacher算法)