Manacher's algorithm
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\)
& \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[]\)数组的处理
- \(\text{ if } (i < r ) \;\;O(1)\) 时间可以确定
- \(\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的更多相关文章
- Manacher's Algorithm 马拉车算法
这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ...
- Manacher’s Algorithm (神啊)
(转载自)http://blog.csdn.net/hopeztm/article/details/7932245 这里描述了一个叫Manacher’s Algorithm的算法. 算法首先将输入字符 ...
- 字符串的最长回文串:Manacher’s Algorithm
题目链接:Longest Palindromic Substring 1. 问题描述 Given a string S, find the longest palindromic substring ...
- Manacher's Algorithm(马拉车算法)
## 背景 该算法用于求字符串的最长回文子串长度. ## 参考文章 >[最长回文子串——Manacher 算法](https://segmentfault.com/a/1190000003914 ...
- 最长子回文字符串(Manacher’s Algorithm)
# # 大佬博客: https://www.cnblogs.com/z360/p/6375514.html https://blog.csdn.net/zuanfengxiao/article/det ...
- 什么是马拉车算法(Manacher's Algorithm)?
提出问题 最长回文子串问题:给定一个字符串,求它的最长回文子串长度. 如果一个字符串正着读和反着读是一样的,那它就是回文串.如a.aa.aba.abba等. 暴力解法 简单粗暴:找到字符串的所有子串, ...
- 【算法总结】Manacher's Algorithm
Manacher's Algorithm针对的是最长回文子串问题.对于此问题,最直接的方法是遍历每一个元素,遍历过程中以每一个字符为中心向两边扩展以寻找此字符为中心的最长回文子串.复杂度O(n2).M ...
- Manacher's Algorithm 马拉车算法(最长回文串)
这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ...
- 马拉车算法(Manacher's Algorithm)
这是悦乐书的第343次更新,第367篇原创 Manacher's Algorithm,中文名叫马拉车算法,是一位名叫Manacher的人在1975年提出的一种算法,解决的问题是求最长回文子串,神奇之处 ...
随机推荐
- C#获取IP和整数IP方法
体验: http://tool.hovertree.com/info/ip/代码如下: using System; using System.Text; using System.Text.Regul ...
- div+css页面右侧底部悬浮层
效果体验:http://hovertree.com/texiao/css/23/ 效果图: 代码如下: <!DOCTYPE html> <html> <head> ...
- js的touch事件的实际引用
一开始做前端页面的时候,接触的也是js,但是随后便被简单高效的jquery吸引过去,并一直使用至今. 而js,则被我主观的认为底层技术而抛弃. 直到这几天工作需要,研究移动端页面的触屏滑动事件,搜索j ...
- 背水一战 Windows 10 (25) - MVVM: 通过 x:Bind 实现 MVVM(不用 Command)
[源码下载] 背水一战 Windows 10 (25) - MVVM: 通过 x:Bind 实现 MVVM(不用 Command) 作者:webabcd 介绍背水一战 Windows 10 之 MVV ...
- Java中, for循环经典例子
循环的两种应用:穷举和迭代. break跳出整个循环 一.穷举:将所有可能的情况都走一遍,用if筛选出符合条件. 百鸡百钱: 一只公鸡1文钱,一只母鸡2文钱,一只小鸡半文钱,需要买100只鸡, 正好花 ...
- css中定位
一切皆为框div.h1或p元素尝尝被称为块级元素.这意味着这些元素显示为一块内容,即“块框”.与之相反,span和strong等元素称为“称为”行内元素“,这是因为他们的内容显示在行中,即”行内框“. ...
- php实现设计模式之 桥接模式
<?php /** 桥接模式:将抽象部分与实现部分分离,使它们都可以独立的变化. * * 在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,桥接模式就是应对这种多维度的变化 */ ...
- 【算法】PHP实现冒泡排序和快速排序--防遗忘
有没有这样的感觉,排序算法虽然简单,但是没看过一次,一会就又忘了,所以有必要 自己使用实际的代码运行实现,才记忆牢固,为此Mark //需求:将数组中元素,从大到小排列$a = array(11, 2 ...
- socket.io,io=Manager(source, opts)
原文:http://www.cnblogs.com/xiezhengcai/p/3968067.html 当我们在使用 var socket = io("ws://103.31.201.15 ...
- 【工业串口和网络软件通讯平台(SuperIO)教程】八.SuperIO通讯机制与设备驱动对接的说明
SuperIO相关资料下载:http://pan.baidu.com/s/1pJ7lZWf 1.1 通讯机制说明 通讯的总体机制采用呼叫应答方式,就是上位机软件主动发送请求数据命令,下位机终端接 ...