Manacher's algorithm 以\(O(n)\)的线性时间求一个字符串的最大回文子串。

1. 预处理

一个最棘手的问题是需要考虑最长回文子串的长度为奇数和偶数的情况。我们通过在任意两个字符之间填充 # 的方法, 将原字符串 \(S\) 转化为辅助字符串 \(T\),具体例子如下:

S = a b a a b a

T = # a # b # a # a # b # a #

转化后便可不必再考虑奇偶问题,同时辅助字符串的长度也变为奇数。

转化后字符串\(T\)的长度为奇数

在长度为奇数的字符串之间(包括外侧),有偶数个位置;在长度为偶数的字符串之间(包括外侧) ,有奇数个位置,所有这样处理之后,字符串的长度都会变为奇数。事实上,公式\(2 \times len + 1\) 已经说明预处理之后的字符串长度必为奇数。

奇回文串和偶回文串一起处理

以字符 # 为中点位置,处理的就是偶回文串的情况,

以其他字符为中点位置,处理的就是奇回文串的情况。

为了避免出现数组访问越界的边界问题,我们将字符串\(T\)的首部再添加一个原字符串\(S\)中没有出现的字符,最后处理完的字符串如下:

S = a b a a b a

T =    # a # b # a # a # b # a #

T = $ # a # b # a # a # b # a #

这样,预处理工作完成,下面进入manacher算法的核心部分。

2. manacher's algorithm

这里,定义一个数组\(p[]\) 和 两个变量 \(r\) 和 \(c\)。

\(p[i]\)表示以位置\(i\)为中点的最大回文子串的长度。

\(r\) 表示当前所有检测过的位置所能到达的最右端。

\(c\) 为与 \(r\) 对应的 \(i\) 的位置,与 \(r\) 同时更新,实际上 \(c+p[i]=r\) 。

回忆下一个\(O(n^2)\)的做法:

从左到右对字符串进行扫描,以每个位置为中点,向两边扩张,并记录最大长度和相应的位置(对于偶数,类似的再处理一遍即可)。

这种算法的空间复杂度为\(O(1)\),是很优秀的,但是,对于每一个位置,都从长度为0开始向两边扩展,这是导致时间复杂度高的一个最主要的原因。

而manacher算法则是额外使用一个\(p[]\)数组记录最大回文子串的长度,

因存在对称关系,数组\(p[]\)的值能够被充分利用,部分\(p[i]\)的值可以在\(O(1)\)的时间确定。

从而使得算法的复杂度降为\(O(n)\)。

这种思路类似于KMP算法,充分利用前面已经匹配过的有用信息。

如何计算数组\(p[]\)的值呢? 我们分两种情况:

\({i}'\) 代表 \(i\) 关于中心 \(c\) 的对称点,计算公式为 \({i}'=2 \times c - i\)

\[\begin{cases}
& \text{ if } (i < r ) \;
\begin{cases}
\text{ if } (r-i>p[{i}']) \; \text{then}\; p[i]=p[{i}'] \\
\text{ otherwise } p[i]= r-i \\
\end{cases} \\ \\
& \text{ otherwise } p[i]= 0
\end{cases}
\]

至于为什么是两种情况,请看下面的参考文献,这里图我就不摆了。

这样我们可以轻松得到P数组的值

T = $ # a # b # a # a # b # a #

P = 0 0 1 0 3 0 1 6 1 0 3 0 1 0

容易看出,\(p[7] = 6\)是数组\(p[]\)中的的最大值,这正是原字符串\(S\)的最长回文子串的长度。这样,在线性时间处理完\(P[]\)数组之后,最大回文子串就找到,若还需要输出字符串,那只需要再做一些细节处理即可。

3. 核心代码

void manacher(char* s)
{
int c = 0, r = 0, len = strlen(s);
p[0] = 0;
for( int i = 1; i < len ; ++i ) {
if( r > i ) p[i] = min( p[ 2 * c - i ], r - i );
else p[i] = 0;
while( i < len && s[i + 1 + p[i]] == s[i - 1 - p[i]] ) p[i]++;
if( i + p[i] > r ) {
r = i + p[i];
c = i;
}
}
}

4. 算法复杂度

从代码可以看出。

\(\text{manacher}\)算法只需要线性扫描一遍预处理后的字符串。

对\(p[]\)数组的处理

  1. \(\text{ if } (i < r ) \;\;O(1)\) 时间可以确定
  2. \(\text{ otherwise } O(n)\)匹配,但是在情况2下,每次扫描都是从\(r+1\)开始的,且\(r\)自身的变化情况是单调递增的,这样可以保证,字符串T中的每个字符最多被访问2次,所以,该算法的时间复杂度是线性\(O(n)\),事实上,\(\text{while}\)循环执行的总次数是线性次的。

5. 参考文献

Manacher's algorithm: 最长回文子串算法

Longest Palindromic Substring Part II

Manacher's ALGORITHM:

练习见 原博客

Manacher's algorithm的更多相关文章

  1. Manacher's Algorithm 马拉车算法

    这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ...

  2. Manacher’s Algorithm (神啊)

    (转载自)http://blog.csdn.net/hopeztm/article/details/7932245 这里描述了一个叫Manacher’s Algorithm的算法. 算法首先将输入字符 ...

  3. 字符串的最长回文串:Manacher’s Algorithm

    题目链接:Longest Palindromic Substring 1. 问题描述 Given a string S, find the longest palindromic substring ...

  4. Manacher's Algorithm(马拉车算法)

    ## 背景 该算法用于求字符串的最长回文子串长度. ## 参考文章 >[最长回文子串——Manacher 算法](https://segmentfault.com/a/1190000003914 ...

  5. 最长子回文字符串(Manacher’s Algorithm)

    # # 大佬博客: https://www.cnblogs.com/z360/p/6375514.html https://blog.csdn.net/zuanfengxiao/article/det ...

  6. 什么是马拉车算法(Manacher's Algorithm)?

    提出问题 最长回文子串问题:给定一个字符串,求它的最长回文子串长度. 如果一个字符串正着读和反着读是一样的,那它就是回文串.如a.aa.aba.abba等. 暴力解法 简单粗暴:找到字符串的所有子串, ...

  7. 【算法总结】Manacher's Algorithm

    Manacher's Algorithm针对的是最长回文子串问题.对于此问题,最直接的方法是遍历每一个元素,遍历过程中以每一个字符为中心向两边扩展以寻找此字符为中心的最长回文子串.复杂度O(n2).M ...

  8. Manacher's Algorithm 马拉车算法(最长回文串)

    这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ...

  9. 马拉车算法(Manacher's Algorithm)

    这是悦乐书的第343次更新,第367篇原创 Manacher's Algorithm,中文名叫马拉车算法,是一位名叫Manacher的人在1975年提出的一种算法,解决的问题是求最长回文子串,神奇之处 ...

随机推荐

  1. 【C#】DataRowState演变备忘

    环境:.net 2.0 DataRow的行状态一段时间不用就会吃不准,记录一下,备查. DataRowState 演变表 行属于如下状态时进行右边操作→ 后的状态演变 添加到表 dt.Rows.Add ...

  2. Linux安装JDK1.7

    发表此篇文章纯属本人愚钝,希望以后再安装JDK不要走那么多曲折的路,也希望可以给后人借鉴. 1.以下以JDK1.7为例 具体官网地址:http://www.oracle.com/technetwork ...

  3. 如何在window下查看文件的md5

    软件的话可以用Hash 不用软件,可以用window自带的命令行,首先在一个目录下按住Shift点击鼠标右键,调出CMD界面(或者直接win+R,cmd),命令行如下: certutil -hashf ...

  4. linux下的目录结构和内容

    用了这么长时间linux系统,有时候哪个文件应该在哪还是不知道的,所以对于根下的目录结构记下: /bin bin是Binary的缩写.这个目录存放着最经常使用的命令. /boot这里存放的是启动Lin ...

  5. 在Eclipse中使用Junit进行单元测试练习 实现最大子数组和算法

    1.如何在MAC OS X下安装配置java开发工具 http://www.cnblogs.com/coderL/p/5939541.html 2.最大子数组和算法 附上程序运行及测试截图,源码见后 ...

  6. SQL多表查询

    设置主键:点击右键设为主键,在默认值那设置newid(),即可自动生成 Join inner join(内连接):select * from 表1 inner join 表2 on 表1.列 = 表2 ...

  7. 记jQuery.fn.show的一次踩坑和问题排查

    最近很少已经很少用jQuery,因为主攻移动端,常用Zepto,其实很多细节和jQuery并不一样.最近又无意中接触到了PC的需求和IE6, 使用了jQuery,刚好踩坑了,特意记录一下. 本文内容如 ...

  8. Sharepoint学习笔记—习题系列--70-576习题解析 -(Q131-Q134)

    Question  131 You are designing multiple SharePoint 2010 features. You have the following requiremen ...

  9. Android中的AlertDialog使用示例四(多项选择确定对话框)

    在Android开发中,我们经常会需要在Android界面上弹出一些对话框,比如询问用户或者让用户选择.这些功能我们叫它Android Dialog对话框,AlertDialog实现方法为建造者模式. ...

  10. 【产品 & 设计】入门 - 工具篇 - Sketch + Skala Preview

    前言 做产品和设计快 1 年了,积累了一点经验分享一下 —— 抛砖引玉,欢迎交流. 声明  欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯伯: ht ...