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#进阶系列】22 CLR寄宿和AppDomain
关于寄宿和AppDomain 微软开发CLR时,将它实现成包含在一个DLL中的COM服务器. 任何Windows应用程序都能寄宿(容纳)CLR.(简单来讲,就是CLR在一个DLL中,通过引用这个DLL ...
- 【转】【译】JavaScript魔法揭秘--探索当前流行框架中部分功能的处理机制
推荐语: 今天推荐一篇华为同事的同事翻译的一篇文章,推荐的主要原因是作为一个华为员工居然晚上还能写文章,由不得小钗不佩服!!! 其中的jQuery.angular.react皆是十分优秀的框架,各有特 ...
- js 数组去重(7种)
第一次写技术博客,之前总是认为写这些会很浪费时间,还不如多看几篇技术博文.但...但昨天不知道受了什么刺激,好像有什么在驱使着自己要写一样,所以才有了今天的第一篇博文.总觉得应该要坚持这样写下去.初次 ...
- 事务隔离级别(IsolationLevel)
事务的特性(ACID)1.原子性(Atomicity) 事物是数据库的逻辑工作单位,事务中的诸多操作要么全做要么全不做2.一致性(Consistency) 事务执行结果必须是使数据库从一个一致性状态变 ...
- SAP RFC
什么是RFC? RFC是SAP系统和其他(SAP或非SAP)系统间的一个重要而常用的双向接口技术,也被视为SAP与外部通信的基本协议.简单地说,RFC过程就是系统调用当前系统外的程序模块,从而实现某个 ...
- 关于arcgis engine的工作空间(IWorkspace)和选择集(FeatureSelection)
1.通过某个WorkspaceFactoryClass(例如AccessWorkspaceFactoryClass)拿到工作空间工厂接口,这时的OpenFromFile方法可以直接打开mdb类型文件, ...
- Android Studio 运行出现 Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.
转载请标明出处: http://www.cnblogs.com/why168888/p/5978381.html 本文出自:[Edwin博客园] 我引用compile 'com.squareup.re ...
- 如何设置TextView控件的背景透明度和字体透明度
如何设置TextView控件的背景透明度和字体透明度 设计师给的标注都是类似这样的: 字号:26 颜色:#000000 透明度:80% 其实,程序上只要需要一个色值就OK了,那么这个色值我如何计算呢? ...
- 【原+转】创建CocoaPods私有podspec
在我的上一篇文章<iOS 手把手教你发布代码到CocoaPods>中着重介绍如何将自己的代码索引添加到公开的CocoaPods中,当你需要主动地向大众开源你的代码时需要那么做.但在现实中我 ...
- Android Activity生命周期与启动模式
Activity的完整生命周期如下图: Activity的加载模式有四种: standard: 标准模式,默认的加载模式,每次通过这种模式启动目标Acitivity,都创建一个新的实例,并将该Acti ...