这个马拉车算法 Manacher‘s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫 Manacher 的人在 1975 年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这是非常了不起的。对于回文串想必大家都不陌生,就是正读反读都一样的字符串,比如 "bob", "level", "noon" 等等,那么如何在一个字符串中找出最长回文子串呢,可以以每一个字符为中心,向两边寻找回文子串,在遍历完整个数组后,就可以找到最长的回文子串。但是这个方法的时间复杂度为 O(n*n),并不是很高效,下面我们来看时间复杂度为 O(n)的马拉车算法。

由于回文串的长度可奇可偶,比如 "bob" 是奇数形式的回文,"noon" 就是偶数形式的回文,马拉车算法的第一步是预处理,做法是在每一个字符的左右都加上一个特殊字符,比如加上 '#',那么

bob    -->    #b#o#b#

noon    -->    #n#o#o#n#

这样做的好处是不论原字符串是奇数还是偶数个,处理之后得到的字符串的个数都是奇数个,这样就不用分情况讨论了,而可以一起搞定。接下来我们还需要和处理后的字符串t等长的数组p,其中 p[i] 表示以 t[i] 字符为中心的回文子串的半径,若 p[i] = 1,则该回文子串就是 t[i] 本身,那么我们来看一个简单的例子:

# 1 # 2 # 2 # 1 # 2 # 2 #
1 2 1 2 5 2 1 6 1 2 3 2 1

为啥我们关心回文子串的半径呢?看上面那个例子,以中间的 '1' 为中心的回文子串 "#2#2#1#2#2#" 的半径是6,而未添加#号的回文子串为 "22122",长度是5,为半径减1。这是个普遍的规律么?我们再看看之前的那个 "#b#o#b#",我们很容易看出来以中间的 'o' 为中心的回文串的半径是4,而 "bob"的长度是3,符合规律。再来看偶数个的情况 "noon",添加#号后的回文串为 "#n#o#o#n#",以最中间的 '#' 为中心的回文串的半径是5,而 "noon" 的长度是4,完美符合规律。所以我们只要找到了最大的半径,就知道最长的回文子串的字符个数了。只知道长度无法定位子串,我们还需要知道子串的起始位置。

我们还是先来看中间的 '1' 在字符串 "#1#2#2#1#2#2#" 中的位置是7,而半径是6,貌似 7-6=1,刚好就是回文子串 "22122" 在原串 "122122" 中的起始位置1。那么我们再来验证下 "bob","o" 在 "#b#o#b#" 中的位置是3,但是半径是4,这一减成负的了,肯定不对。所以我们应该至少把中心位置向后移动一位,才能为0啊,那么我们就需要在前面增加一个字符,这个字符不能是#号,也不能是s中可能出现的字符,所以我们暂且就用美元号吧,毕竟是博主最爱的东西嘛。这样都不相同的话就不会改变p值了,那么末尾要不要对应的也添加呢,其实不用的,不用加的原因是字符串的结尾标识为 '\0',等于默认加过了。那此时 "o" 在 "\$#b#o#b#" 中的位置是4,半径是4,一减就是0了,貌似没啥问题。我们再来验证一下那个数字串,中间的 '1' 在字符串 "\$#1#2#2#1#2#2#" 中的位置是8,而半径是6,这一减就是2了,而我们需要的是1,所以我们要除以2。之前的 "bob" 因为相减已经是0了,除以2还是0,没有问题。再来验证一下 "noon",中间的 '#' 在字符串 "$#n#o#o#n#" 中的位置是5,半径也是5,相减并除以2还是0,完美。可以任意试试其他的例子,都是符合这个规律的,最长子串的长度是半径减1,起始位置是中间位置减去半径再除以2。

那么下面我们就来看如何求p数组,需要新增两个辅助变量 mx 和 id,其中 id 为能延伸到最右端的位置的那个回文子串的中心点位置,mx 是回文串能延伸到的最右端的位置,需要注意的是,这个 mx 位置的字符不属于回文串,所以才能用 mx-i 来更新 p[i] 的长度而不用加1,由 mx 的更新方式 mx = i + p[i] 也能看出来 mx 是不在回文串范围内的,这个算法的最核心的一行如下:

p[i] = mx > i ? min(p[ * id - i], mx - i) : ;

可以这么说,这行要是理解了,那么马拉车算法基本上就没啥问题了,那么这一行代码拆开来看就是

如果 mx > i, 则 p[i] = min( p[2 * id - i] , mx - i )

否则,p[i] = 1

当 mx - i > P[j] 的时候,以 S[j] 为中心的回文子串包含在以 S[id] 为中心的回文子串中,由于 i 和 j 对称,以 S[i] 为中心的回文子串必然包含在以 S[id] 为中心的回文子串中,所以必有 P[i] = P[j],其中 j = 2*id - i,因为 j 到 id 之间到距离等于 id 到 i 之间到距离,为 i - id,所以 j = id - (i - id) = 2*id - i,参见下图。

 

当 P[j] >= mx - i 的时候,以 S[j] 为中心的回文子串不一定完全包含于以 S[id] 为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以 S[i] 为中心的回文子串,其向右至少会扩张到 mx 的位置,也就是说 P[i] = mx - i。至于 mx 之后的部分是否对称,就只能老老实实去匹配了,这就是后面紧跟到 while 循环的作用。

对于 mx <= i 的情况,无法对 P[i] 做更多的假设,只能 P[i] = 1,然后再去匹配了。

参见如下实现代码:

#include <vector>
#include <iostream>
#include <string> using namespace std; string Manacher(string s) {
// Insert '#'
string t = "$#";
for (int i = ; i < s.size(); ++i) {
t += s[i];
t += "#";
}
// Process t
vector<int> p(t.size(), );
int mx = , id = , resLen = , resCenter = ;
for (int i = ; i < t.size(); ++i) {
p[i] = mx > i ? min(p[ * id - i], mx - i) : ;
while (t[i + p[i]] == t[i - p[i]]) ++p[i];
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
resCenter = i;
}
}
return s.substr((resCenter - resLen) / , resLen - );
} int main() {
string s1 = "";
cout << Manacher(s1) << endl;
string s2 = "";
cout << Manacher(s2) << endl;
string s = "waabwswfd";
cout << Manacher(s) << endl;
}

应用实例:

Longest Palindromic Substring

Manacher's Algorithm 马拉车算法的更多相关文章

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

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

  2. HDU3068 最长回文 Manacher's Algorithm 马拉车算法 模板

    HDU3068 复习了一下这个算法, 注意数组大小要开两倍大. #include <algorithm> #include <iterator> #include <io ...

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

    作用:求一个字符串中的最长子串,同时还可以求所有子串的长度. 题目链接: https://vjudge.net/contest/254692#problem/B 最长回文串长度的代码: int Man ...

  4. Manacher's Algorithm 马拉车算法

    作用:求一个字符串中的最长子串,同时还可以求所有子串的长度. 题目链接: https://vjudge.net/contest/254692#problem/B 代码: #include<bit ...

  5. Manacher’s Algorithm (神啊)

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

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

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

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

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

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

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

  9. manacher(马拉车算法)

    Manacher(马拉车算法) 序言 mannacher 是一种在 O(n)时间内求出最长回文串的算法 我们用暴力求解最长回文串长度的时间复杂度为O(n3) 很明显,这个时间复杂度我们接受不了,这时候 ...

随机推荐

  1. JavaScript面向对象与原型

    工厂模式:无法识别对象 function createObject(name, age) { //集中实例化的函数 var obj = new Object(); obj.name = name; o ...

  2. 【转】visio中关于shape属性的修改和读取

    PS:  本文转自: http://blog.sina.com.cn/s/blog_6bcfb9420100wzxf.html visio中都是shape,shape就是一个对象,要想实现对shape ...

  3. iOS 事件处理之UIResponder简介

    在用户使用app过程中,会产生各种各样的事件 iOS中的事件可以分为3大类型:触摸事件.加速计事件.远程控制事件 在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处 ...

  4. MyEclipse 2016 CI 4新增BootStrap模板

    Live Preview with CodeLive 目前CodeLive还只有Live Preview这一个功能,在后续的版本中会陆续添加新功能. 新增Bootstrap模板 在模板面板中选择相应的 ...

  5. string中Insert与Format效率对比、String与List中Contains与IndexOf的效率对比

    关于string的效率,众所周知的恐怕是“+”和StringBuilder了,这些本文就不在赘述了.关于本文,请先回答以下问题(假设都是基于多次循环反复调用的情况下):1.使用Insert与Forma ...

  6. 全文检索引擎 Solr 部署与基本原理

    全文检索引擎 Solr 部署与基本原理 搜索引擎Solr环境搭建实例 关于 solr , schema.xml 的配置说明 全文检索引擎Solr系列-–全文检索基本原理 一.搜索引擎Solr环境搭建实 ...

  7. IOS 日期的简洁格式展示

    首先我要解释一下标题的意义,日期的简洁格式展示,之所以简介,是因为让人一目了然,不需要思考是什么时候. 在详细一点就是我们在微信朋友圈中 所看到的时间格式. 例如:刚刚 -几分钟前-几小时前等等. 今 ...

  8. vagrant vbox上配置好开发环境缓存问题

    vagrant配置完成 设置好共享目录 搭建好nginx环境 访问 127.0.0.1:8080 一切正常  然后进入本的的开发目录修改测试文件保存后刷新页面 问题来了..........没变化  然 ...

  9. 深入解析SQL Server并行执行原理及实践(下)

    谈完并行执行的原理,咱们再来谈谈优化,到底并行执行能给我们带来哪些好处,我们又应该注意什么呢,下面展开. Amdahl’s  Law 再谈并行优化前我想有必要谈谈阿姆达尔定律,可惜老爷子去年已经驾鹤先 ...

  10. AES —— JAVA中对称加密和解密

    package demo.security; import java.io.IOException; import java.io.UnsupportedEncodingException; impo ...