Manacher详解

之前的字符串题解中对Manacher的思想进行了简略的介绍,在这篇文章中,我将会详细的将这个算法的初衷和具体实现理论进行解释。声明一点,这是我个人的理解,可能有不全面之处,望多包涵。在之前的几篇文章中,我也发现有个别的编辑错误,希望大家在看的时候多加思考,不要被我的思维禁锢。



可能有的人没有看过之前的文章,那么我再赘述几句。

回文串:以最中间一个字符或者中轴线为对称轴左右两侧镜像相等的字符串。

例如 :

123321 这个字符串前三个字符和后三个字符镜像相等

1234321 这个字符串 前四个字符和后四个字符镜像相等。

从上面的例子可以看出,回文串有两种可能,一种是偶对称,即对称轴的位置没有字符,并且整个字符串的字符个数是偶数;另一种是奇对称,即对称轴的位置是一个字符,并且整个字符串的字符个数是奇数。普通的处理方法是将两种情况分开讨论,逐个枚举回文中心,由中心向两边扩散,确定回文串的长度。

Manacher算法就是在这个基础上进行改进的,他的改进主要有两个方面:

1??上述方法需要将情况分为奇对称和偶对称两种情况,比较麻烦,Manacher将原字符串进行了些微的改进,就可以用统一的方法对得到的新字符串进行处理。方法如下:

奇数和偶数之和必定为奇数,那么,在一个字符串的首尾和每两个字符之间加入一个标记符,就可以将所有的偶对称子串处理为奇对称串,这样仅用奇对称判断就可以处理偶对称的问题。

举例如下:

原串 123321

新串 *1*2*3*3*2*1*

将原来的6字符变成了13字符

原串 1234321

新串 *1*2*3*4*3*2*1*

将原来的7字符变成了15字符

如上所示,所有字符巧妙的变成了奇对称。如果原来回文串的字符个数是奇数,那么改进后的回文串的对称中心一定是字母,且该字母一定也是原回文串的对称中心。如果原来回文串的字符个数是偶数,那么改进后的回文串对称中心一定是插入的特殊符号(本文中为*),且该符号的位置在原串中是对称轴的位置。

2??之前的办法是将每个字符作为回文中心来确定以该字符为中心的回文串长度,也就意味着需要无数次的枚举和比较。Manacher 的第二个改进方向就是,利用之前确定的回文串的特征,减少本回文中心所在回文串确定长度时的判断次数。

现在就有两个问题,第一,既然要借助之前求得的回文串特征,那么在这个过程中必然应该将之前求得的所有回文串的信息记录下来,至少应该记录其回文中心和回文串长度吧?Manacher 选择记录的是回文串中心和回文半径(回文串的一半长度),这个过程是用一个叫做 p[]数组的以及它的下标完成的,如p[ i ]就表示以 i 为回文中心的字符串的回文半径为 p[ i ]。第二,之前已经判断过很多个回文串了,到底借助哪个回文串效果最好呢。

如下:

如果现在要判断A位置的回文长度,idx,i,i‘ 这三个中心的回文串长度已经得知,那么应该借助以idx为回文中心的这个回文串还是以 i 为回文中心的回文串,或者是 以 i’ 为中心的回文串?答案是 选择 idx 为中心的回文串,这个时候就有人会说:idx 那个回文串长度最长,覆盖面最广,直觉上来讲,就应该选它。

如果你也这么想,那请看下面这个

这个时候,求 A 点处的回文半径,应该借助哪个回文串呢?如果这时候你的答案还是 idx ,那我可以明确地告诉你:宝贝,这次你真的猜错了。因为这个时候需要借助的是 B 为中心的回文串了。为什么会这样?因为要求 A 所在的回文串长度,必然得借助和它关系最大的一个回文串,这样才能尽可能保证借助到的长度最多,正如上图所展示,B 比 idx 的右边界更远一些,也就意味着,B 对“前方的道路”更了解一些,尽管idx确实在长度上占优势,在A的左侧 idx所在回文串长度远远长于B所在回文串,但在A点的右侧,B的长度更多一些。回文中心左右两侧元素个数是相等的,即模版回文串分布在A点左右两侧可以用得到的字符个数是相等的(以最少的一方计算),从这个角度来看,选B更合理一些。由此可以得出一个结论:求当前中心所在回文串长度,要借助的模版必定是已经求得的回文串中右边界最远的回文串。

知道了需要借助哪个模版串之后,接下来就需要讨论如何借助模版串的问题。想知道如何借助模版串,首先应该清楚的是模版串和当前要求的回文中心的位置关系,这样才好下手。

模版串和当前回文中心的位置关系共有四种可能:

  1. 在第一种情况中,选择的模版串中心是 idx,其右边界为 idx右 ,i‘ 是 i 关于 idx 对称的点,由于idx是回文串,因此,在 idx串 内部 i 和 i’ 的对称情况是一样的,可以跳过这部分比较提高效率,但是在 idx 串之外i‘ 的情况 和 i 的情况是否一样则无法确定,因此需要在 idx右边界之外 对 i 的对称情况进行确定。这种情况中 i 串 最短的可能长度是 idx右-i ,在此基础上对 idx右的右侧和 i -(idx右 - i )的左侧进行处理即可。

  2. 在第二种情况中,选择的模版串 idx ,其 右边界 记为 idx右,i’ 是 i 关于 idx 的对称点,同上,在 idx 内部,i‘ 和 i 的对称情况一样,因此在上图这种情况下, i 串 的回文半径至少等于 i’ 的回文半径,但在idx右的右侧,是否还有字符使 i 回文不可知,因此 在i‘回文长度的基础上,对 idx右 的右侧和 i - ( idx右 - i )的左侧进行处理即可。

3.

在第三种情况中,选择的模版串 idx ,其右边界记为 idx右, i‘ 是 i 关于 idx 的对称点,同上,在 idx 内部,i’ 和 i 的对称情况是一样的,又因为 i 和 i‘ 串完全被 idx 串包含,因此对称情况完全一样,即 i 串 回文长度 最终等于 i‘ 串的长度

4.

在第四种情况中,i 串 完全脱离之前已经求得的所有回文串,因此只能以 i 为中心在其左右两侧逐个判断。



以上是理论知识的介绍,下面我将具体实现代码给大家进行解读,要说一点,代码的实现和理论知识略有不同,代码的实现将上述情况进行了综合,把其中相同的操作进行合并。

主要声明:

const int maxn; // 存放字符串长度可能的最大值

char str[ 3 * maxn ] , //存放处理后的新字符串

s[ maxn ] ; //存放原串

int p[ 3 * maxn ];存放新串每个字符对应的回文半径长度

int len1, //存放原串的长度;

len2 ; //存放处理后新串的长度-1;

Manacher的代码实现分为两个部分。

1??第一部分 生成新串

2??第二部分 生成p[]数组



代码很精简,但逻辑性特别强,方法也很独特。今天的分享到此结束,感谢您的阅读。

时间: 2025-01-07 10:05:33

Manacher详解的相关文章

Manacher算法详解

[转] Manacher算法详解 转载自: http://blog.csdn.net/dyx404514/article/details/42061017 Manacher算法 算法总结第三弹 manacher算法,前面讲了两个字符串相算法——kmp和拓展kmp,这次来还是来总结一个字符串算法,manacher算法,我习惯叫他 “马拉车”算法. 相对于前面介绍的两个算法,Manacher算法的应用范围要狭窄得多,但是它的思想和拓展kmp算法有很多共通支出,所以在这里介绍一下.Manacher算法

Spring事务管理(详解+实例)

写这篇博客之前我首先读了<Spring in action>,之后在网上看了一些关于Spring事务管理的文章,感觉都没有讲全,这里就将书上的和网上关于事务的知识总结一下,参考的文章如下: Spring事务机制详解 Spring事务配置的五种方式 Spring中的事务管理实例详解 1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都

转载:DenseNet算法详解

原文连接:http://blog.csdn.net/u014380165/article/details/75142664 参考连接:http://blog.csdn.net/u012938704/article/details/53468483 本文这里仅当学习笔记使用,具体细节建议前往原文细度. 论文:Densely Connected Convolutional Networks 论文链接:https://arxiv.org/pdf/1608.06993.pdf 代码的github链接:h

MariaDB(MySQL)创建、删除、选择及数据类型使用详解

一.MariaDB简介(MySQL简介略过) MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可 MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品.在存储引擎方面,使用XtraDB(英语:XtraDB)来代替MySQL的InnoDB. MariaDB由MySQL的创始人Michael Widenius(英语:Michael Widenius)主导开发,他早前曾以10亿美元的价格,将自己创建的公司MySQL A

HttpServletResponse和HttpServletRequest详解

HttpServletResponse,HttpServletRequest详解 1.相关的接口 HttpServletRequest HttpServletRequest接口最常用的方法就是获得请求中的参数,这些参数一般是客户端表单中的数据.同时,HttpServletRequest接口可以获取由客户端传送的名称,也可以获取产生请求并且接收请求的服务器端主机名及IP地址,还可以获取客户端正在使用的通信协议等信息.下表是接口HttpServletRequest的常用方法. 说明:HttpServ

POSIX 线程详解(经典必看)

总共三部分: 第一部分:POSIX 线程详解                                   Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  2000 年 7 月 01 日 第二部分:通用线程:POSIX 线程详解,第 2部分       Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  20

.NET深入解析LINQ框架(五:IQueryable、IQueryProvider接口详解)

阅读目录: 1.环路执行对象模型.碎片化执行模型(假递归式调用) 2.N层对象执行模型(纵横向对比链式扩展方法) 3.LINQ查询表达式和链式查询方法其实都是空壳子 4.详细的对象结构图(对象的执行原理) 5.IQueryable<T>与IQueryProvider一对一的关系能否改成一对多的关系 6.完整的自定义查询 1]. 环路执行对象模型.碎片化执行模型(假递归式调用) 这个主题扯的可能有点远,但是它关系着整个LINQ框架的设计结构,至少在我还没有搞懂LINQ的本意之前,在我脑海里一直频

netstat状态详解

一.生产服务器netstat tcp连接状态................................................................................ 2 1.1生产服务器某个业务LVS负载均衡上连接状态数量............................................... 2 1.2生产服务器某个业务web上连接状态数量...............................................

详解go语言的array和slice 【二】

上一篇  详解go语言的array和slice [一]已经讲解过,array和slice的一些基本用法,使用array和slice时需要注意的地方,特别是slice需要注意的地方比较多.上一篇的最后讲解到创建新的slice时使用第三个索引来限制slice的容量,在操作新slice时,如果新slice的容量大于长度时,添加新元素依然后使源的相应元素改变.这一篇里我会讲解到如何避免这些问题,以及迭代.和做为方法参数方面的知识点. slice的长度和容量设置为同一个值 如果在创建新的slice时我们把