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#操作Mongodb
因为MongoDb 跨平台,可以免费使用,读写效率高,集群搭建简单,可以水平扩展等各种因素. 我决定研究一下Mongodb,在查看了相关文档后发现它对C#的支持不错,而且还有现成的C#的驱动, 新版的 ...
- html5手机端的点击弹出侧边滑动菜单代码
效果预览:http://hovertree.com/texiao/html5/19/ 本效果适用于移动设备,可以使用手机等浏览效果. 源码下载:http://hovertree.com/h/bjaf/ ...
- 解决域名DNS解析的故障
在实际应用过程中可能会遇到DNS解析错误的问题,就是说当我们访问一个域名时无法完成将其解析到IP地址的工作,而直接输入网站IP却可以正常访问,这就是因为DNS解析出现故障造成的.这个现象发生的机率比较 ...
- 背水一战 Windows 10 (17) - 动画: ThemeTransition(过渡效果)
[源码下载] 背水一战 Windows 10 (17) - 动画: ThemeTransition(过渡效果) 作者:webabcd 介绍背水一战 Windows 10 之 动画 ThemeTrans ...
- eclipse中egit插件使用
这篇文章当时制作有点粗糙,建议阅读升级版:eclipse中egit插件使用--升级版 使用git作为项目的代码管理工具现在是越来越火,网上有各种各样的文章.博客.讨论,其中以命令行居多.使用eclip ...
- [原创小工具]软件内存、CPU使用率监视,应用程序性能监测器 v3.0 绿色版
应用程序性能监测器 V3.0 更新内容: 1.对一些代码进行了修改,软件本身的性能有所提升. 应用程序性能监测器 V2.0 更新内容: 1.鼠标移动到曲线区域,显示相关的曲线值 ...
- 使用java操作MongoDB
1.环境准备 下载mongoDB对Java支持的驱动包 驱动包下载地址:https://github.com/mongodb/mongo-java-driver/downloads 2.查询集合中所有 ...
- 《对象及DOM知识点及其应用1》
1.图片间的来回切换用if{}else{}; <超链接方式的切换图片(常用)>如:<a href="../images1/1-small.jpg" id=&quo ...
- Netty(五)序列化protobuf在netty中的使用
protobuf是google序列化的工具,主要是把数据序列化成二进制的数据来传输用的.它主要优点如下: 1.性能好,效率高: 2.跨语言(java自带的序列化,不能跨语言) protobuf参考文档 ...
- 更改Visual Studio 2015 默认的语言设置
Vs支持多种语言,但有可能创建项目时,默认的开发语言不是你需要的,比如:默认是Visual C++ 你可以通过"工具"----选项----导入和导出设置来修改. 引用: https ...