什么是马拉车算法(Manacher's Algorithm)?
提出问题
最长回文子串问题:给定一个字符串,求它的最长回文子串长度。
如果一个字符串正着读和反着读是一样的,那它就是回文串。如a、aa、aba、abba等。
暴力解法
简单粗暴:找到字符串的所有子串,遍历每一个子串以验证它们是否为回文串。一个子串由子串的起点和终点确定,对于一个长度为n的字符串,共有n^2个子串。这些子串的平均长度大约是n/2,因此这个解法的时间复杂度是 \(O(n^3)\)。明显不可取。
方法改进
回文子串是连续的,而且是对称的。长度为奇数回文串以最中间字符的位置为对称轴左右对称,而长度为偶数的回文串的对称轴在中间两个字符之间的空隙。可否利用这种对称性来提高算法效率呢?答案是肯定的。我们知道整个字符串中的所有字符,以及字符间的空隙,都可能是某个回文子串的对称轴位置。可以遍历这些位置,在每个位置上同时向左和向右扩展,直到左右两边的字符不同,或者达到边界。对于一个长度为n的字符串,这样的位置一共有 n+n-1=2n-1 个,在每个位置上平均大约要进行 n/4 次字符比较,于是此算法的时间复杂度是 \(O(n^2)\)。
另外一种改进方法是利用动态规划,DP[i][j]定义成子串[i, j]是否是回文串。外循环 i从 n−1 往 0 遍历,内循环 j 从 i 往 n−1 遍历,若s[i]==s[j]:
- 若i==j,则dp[i][j]=true;
- 若i和j是相邻的,则dp[i][j]=true;
- 若i和j中间只有一个字符,则dp[i][j]=true;
- 否则,检查dp[i+1][j-1]是否为true,若为true,那么dp[i][j]就是true。
前三条可以合并,即 j−i≤2。求得dp[i][j]真值后,也可快速解决问题。时间复杂度:\(O(n^2)\)。
Manacher's Algorithm
对于 \(O(n^2)\) 的复杂度,或许还不满足,是否可以再优化一些呢?
先分析改进方法中的缺陷,利用回文中心需要分奇偶两种情况讨论,两种改进都会重复访问子串,降低效率。
Manacher's Algorithm正是针对这两个问题进行进一步的改进,将时间复杂度降到了神奇的 \(O(n)\):
问题一:回文长度奇偶性问题
为了不区分奇偶两种情况,对字符串作预处理,在所有字符之间(包括首尾)插入相同字符如'#',处理之后所有的子串都是奇数长度的。如aba→#a#b#a#。
插入的是同样的符号,且符号不存在于原串,因此子串的回文性不受影响,原来是回文的串,插完之后还是回文的,原来不是回文的,依然不会是回文。
问题二:重复访问问题
定义回文半径:回文串中最左或最右位置的字符与其对称轴的距离。算法中定义回文半径数组 \(RL\),\(RL[i]\) 表示以第i个字符为对称轴的回文串的回文半径。
定义 \(MaxRight\),表示当前访问到的所有回文子串,所能触及的最右一个字符的位置。另外还要记录下 \(MaxRight\) 对应的回文串的对称轴所在的位置,记为 \(pos\)。
核心代码:RL[i] = i < MaxRight ? min(RL[2*pos-i], MaxRight-i) : 1;
理解了这行代码,可以说就理解了这个算法。我们来分情况讨论:
(1)i < MaxRight时,可以再分两种情况:
①MaxRight - i > RL[2*pos-i],如下图,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j]。

②MaxRight - i < RL[2*pos-i],如下图,以S[j]为中心的回文子串不一定完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。

(2)i > MaxRight时,无法对 P[i]做更多的假设,只能P[i] = 1,然后再去慢慢匹配了。
代码实现
返回最长的回文子串。代码中resLen为处理后字符串的最大回文半径,对应到原来的字符串中时,只需-1即是整个回文串的长度。
string Manacher(string s) {
    //预处理
    string t = "#";
    for (int i = 0; i < s.size(); ++i) {
        t += s[i];
        t += "#";
    }
    vector<int> RL(t.size(), 0);
    int MaxRight = 0, pos = 0;
    int resLen = 0, resCenter = 0;
    for (int i = 0; i < t.size(); ++i) {
        RL[i] = MaxRight > i ? min(RL[2 * pos - i], MaxRight - i) : 1;
        while (i-RL[i] >=0 && i+RL[i] < t.size() && t[i + RL[i]] == t[i - RL[i]])//扩展,注意边界
            ++RL[i];
        //更新最右端及其中心
        if (MaxRight < i + RL[i] -1) {
            MaxRight = i + RL[i] -1;
            pos = i;
        }
        if (resLen < RL[i]) {
            resLen = RL[i];
            resCenter = i;
        }
    }
    return s.substr((resCenter - resLen + 1) / 2 , resLen - 1);
}
时间复杂度:\(O(n)\)。在参考链接中有比较详细的证明过程。
参考链接:https://segmentfault.com/a/1190000003914228
参考链接:http://www.cnblogs.com/grandyang/p/4475985.html
什么是马拉车算法(Manacher's Algorithm)?的更多相关文章
- A - 最长回文(马拉车算法//manacher)
		给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.回文就是正反读都是一样的字符串,如aba, abba等 Input输入有多组case,不超过120组,每组输入为一 ... 
- Manacher's Algorithm 马拉车算法
		这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ... 
- Manacher's Algorithm 马拉车算法(最长回文串)
		这个马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这 ... 
- 马拉车算法(Manacher's Algorithm)
		这是悦乐书的第343次更新,第367篇原创 Manacher's Algorithm,中文名叫马拉车算法,是一位名叫Manacher的人在1975年提出的一种算法,解决的问题是求最长回文子串,神奇之处 ... 
- Manacher's Algorithm(马拉车算法)
		## 背景 该算法用于求字符串的最长回文子串长度. ## 参考文章 >[最长回文子串——Manacher 算法](https://segmentfault.com/a/1190000003914 ... 
- manacher(马拉车算法)
		Manacher(马拉车算法) 序言 mannacher 是一种在 O(n)时间内求出最长回文串的算法 我们用暴力求解最长回文串长度的时间复杂度为O(n3) 很明显,这个时间复杂度我们接受不了,这时候 ... 
- 【算法总结】Manacher's Algorithm
		Manacher's Algorithm针对的是最长回文子串问题.对于此问题,最直接的方法是遍历每一个元素,遍历过程中以每一个字符为中心向两边扩展以寻找此字符为中心的最长回文子串.复杂度O(n2).M ... 
- HDU - 3068 最长回文manacher马拉车算法
		# a # b # b # a # 当我们遇到回判断最长回文字符串问题的时候,若果用暴力的方法来做,就是在字符串中间添加 #,然后遍历每一个字符,找到最长的回文字符串.那么马拉车算法就是在这个基础上进 ... 
- Manacher(马拉车)算法(jekyll迁移)
		layout: post title: Manacher(马拉车)算法 date: 2019-09-07 author: xiepl1997 cover: 'assets/img/manacher.p ... 
随机推荐
- 在 Ruby 中执行 Shell 命令的 6 种方法
			我们时常会与操作系统交互或在 Ruby 中执行 Shell 命令.Ruby为我们提供了完成该任务的诸多方法. Exec Kernel#exec 通过执行给定的命令来替换当前进程,例如: $ irb & ... 
- 27-python 画图
			绝佳教程:http://pyecharts.org/#/zh-cn/prepare?id=%E4%BD%BF%E7%94%A8%E4%B8%BB%E9%A2%98安装 pyecharts pip in ... 
- Resolving multicopy duplications de novo using polyploid phasing  用多倍体相位法解决多拷贝复制的新问题
			抽象.虽然单分子测序系统的兴起已经实现组装复杂地区的能力空前提高在基因组中,基因组中的长节段重复仍然是装配中具有挑战性的前沿. 分段重复同时具有丰富的基因并且倾向于大的结构重排,使得它们的序列的分辨率 ... 
- 转载:Candy? 在线性时间内求出素数与欧拉函数
			转载自:http://www.cnblogs.com/candy99/p/6200660.html 2818: Gcd Time Limit: 10 Sec Memory Limit: 256 MB ... 
- $_SERVER["HTTP_HOST"]
			$_SERVER["HTTP_HOST"]访问的网站的域名 
- python pip 代理设置
			pip install --proxy="user:password@server:port" packagename origin url: http://xiuxixiuxi. ... 
- IRC聊天指南
			参考https://www.cnblogs.com/fzzl/archive/2011/12/26/2302637.html 
- Zookeeper基本使用(转)
			一.Zookeeper架构 云计算越来越流行的今天,单一机器处理能力已经不能满足我们的需求,不得不采用大量的服务集群.服务集群对外提供服务的过程中,有很多的配置需要随时更新,服务间需要协调工作,这些信 ... 
- C++中函数模版与类模版
			1.什么是模板? (1)可以这样来解释这个问题,例如当我们需要定义多个函数,而这个函数功能其实都是一样的,例如两个数相加的函数, 只是相加的两个数的类型不相同而已,这就导致我们需要定义多个函数:当我们 ... 
- C++ 类 & 对象-C++ 内联函数-C++ this 指针-C++ 类的静态成员
			C++ 内联函数 C++ 内联函数是通常与类一起使用.如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方. 对内联函数进行任何修改,都需要重新编译函数的所有客户端 ... 
