马拉车算法(Manacher's Algorithm)
这是悦乐书的第343次更新,第367篇原创
Manacher's Algorithm,中文名叫马拉车算法,是一位名叫Manacher的人在1975年提出的一种算法,解决的问题是求最长回文子串,神奇之处在于将算法的时间复杂度精进到了O(N)
,下面我们来详细介绍下这个算法的思路。
01 算法由来
在求解最长回文子串的问题时,一般的思路是以当前字符为中心,向其左右两边扩展寻找回文,但是这种解法的时间复杂度是O(N^2),那么能不能将时间复杂度再降低一点?做到线性?马拉车算法就完美地解决了这个问题。
02 预处理
回文字符串以其长度来分,可以分为奇回文(其长度为奇数)、偶回文(其长度为偶数),一般情况下需要分两种情况来寻找回文,马拉车算法为了简化这一步,对原始字符串进行了处理,在每一个字符的左右两边都加上特殊字符(肯定不存在于原字符串中的字符),让字符串变成一个奇回文。例如:
原字符串:abba,长度为4
预处理后:#a#b#b#a#,长度为9
原字符串:aba,长度为3
预处理后:#a#b#a#,长度为7
03 计算最长回文子串长度
以字符串"cabbaf"
为例,将预处理后的新字符串"#c#a#b#b#a#f#"
变成一个字符数组arr,定义一个辅助数组int[] p
,p
的长度与arr
等长,p[i]
表示以arr[i]
字符为中心的最长回文半径,p[i]=1
表示只有arr[i]
字符本身是回文子串。
i 0 1 2 3 4 5 6 7 8 9 10 11 12
arr[i] # c # a # b # b # a # f #
p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1
我们来比对分下一下最长回文半径和原字符串之间的关系。在上面例子中,最长回文子串是"#a#b#b#a#"
,它以arr[6]为中心,半径是5,其代表的原始字符串是"abba"
,而"abba"
的长度为4,可以通过5减去1得到,是字符串"cabbaf"
中的最长回文子串,那么我们是不是可以得出最长回文半径和最长回文子串长度之间的关系?
让我们再多看几个例子,如"aba"
,转换后是"#a#b#a#"
,以字符'b'
为中心的回文,半径是4,减1得到3,3是原字符串的最长回文子串长度。
再例如"effe"
,转换后是"#e#f#f#e#"
,以最中间的'#'为中心的回文,半径是5,减1得到4,4是原字符串的最长回文子串长度。
因此,最后我们得到最长回文半径和最长回文子串长度之间的关系:int maxLength = p[i]-1
。maxLength
表示最长回文子串长度。
04 计算最长回文子串起始索引
知道了最长回文子串的长度,我们还需要知道它的起始索引值,这样才能截取出完整的最长回文子串。
继续以第三步中的字符串"cabbaf"
为例,p[6]=5
,是最长半径,用6(i)减去最长半径5(p[i])得到1,而1恰好是最长回文子串"abba"的起始索引。
我们再来看一个奇回文的例子。例如"aba"
,转换后是"#a#b#a#"
,p[3]=4,最长半径是4,i为3,用i减去4得到-1,数组下标越界了。
在偶回文的情况下,可以满足i减最长半径,而奇回文却会下标越界,我们需要在转换后的字符串前面再加一个字符,解决下标越界的问题,不能是'#'
,那就加个'$'
字符吧,但是加过一个字符后,字符串的长度不是奇数了,只能在尾部再加一个不会重复出现的字符,比如'@'
,这样字符串的长度依旧是奇数了,满足前面第三部分的条件。
加多一个字符后,奇回文可以正常做减法了,偶回文呢?
i 0 1 2 3 4 5 6 7 8 9 10 11 12 13
arr[i] $ # c # a # b # b # a # f #
p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1
在补上字符'$'
后,p[7]=5,用i减去最长半径,7-5=2,而理想的结果应该是1,那就再除以2吧,这样就能得到1了。而奇回文"aba"在用i减去最长半径后得到的是0,除以2后还是0,可以完美解决下标越界的问题。
结论:最长回文子串的起始索引int index = (i - p[i])/2
。
05 计算p数组
在第三步和第四步中我们都用到了一个关键对象p数组,存放的是最长回文子串半径,那么它是怎么来的呢?
还是以上面的例子配合着看,
i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
arr[i] $ # c # a # b # b # a # f # @
p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1
设置两个变量id和mx,id是所有回文子串中,能延伸到最右端位置的那个回文子串的中心点位置,mx是该回文串能延伸到的最右端的位置。
当i等于7时,id等于7,p[id] = 5,在以位置7为中心的回文子串中,该回文子串的右边界是位置12。
当i等于12时,id等于12,p[id] = 2,在以位置12为中心的回文子串中,该回文子串的右边界是位置14。
由此我们可以得出回文子串右边界和其半径之间的关系:mx = p[id]+id
。
因为回文字符串是中心对称的,知道中心点位置id,如果一个位置的回文子串以i为中心,并且包含在以id为中心的回文子串中,即mx > i
,那么肯定会存在另外一个以j为中心回文子串,和以i为中心的回文子串相等且对称,即p[j] = p[i]
,而i和j是以id为中心对称,即i+j=2*id
,如果知道了i的值,那么j = 2*id - i
。
但是我们需要考虑另外一种情况,如果存在一个以i为中心的回文子串,依旧有mx > i,但是以i为中心的回文子串右边界超过了mx,在i到mx的这段回文子串中,与另一端对称的以j为中心的回文子串还是相等的,此时p[i] = mx - i
,p[j] = [pi]
,至于右边界mx之外的子串,即以i为中心的回文子串超出的部分是否还是满足上述条件就需要遍历比较字符了。
因此,在mx > i
的情况下,p[i] = Math.min(p[2*id - i], mx - i)
。
另外如果i大于mx了,也即是边界mx后面的子串,依旧需要去比较字符计算。
public static String Manacher(String s) {
if (s.length() < 2) {
return s;
}
// 第一步:预处理,将原字符串转换为新字符串
String t = "$";
for (int i=0; i<s.length(); i++) {
t += "#" + s.charAt(i);
}
// 尾部再加上字符@,变为奇数长度字符串
t += "#@";
// 第二步:计算数组p、起始索引、最长回文半径
int n = t.length();
// p数组
int[] p = new int[n];
int id = 0, mx = 0;
// 最长回文子串的长度
int maxLength = -1;
// 最长回文子串的中心位置索引
int index = 0;
for (int j=1; j<n-1; j++) {
// 参看前文第五部分
p[j] = mx > j ? Math.min(p[2*id-j], mx-j) : 1;
// 向左右两边延伸,扩展右边界
while (t.charAt(j+p[j]) == t.charAt(j-p[j])) {
p[j]++;
}
// 如果回文子串的右边界超过了mx,则需要更新mx和id的值
if (mx < p[j] + j) {
mx = p[j] + j;
id = j;
}
// 如果回文子串的长度大于maxLength,则更新maxLength和index的值
if (maxLength < p[j] - 1) {
// 参看前文第三部分
maxLength = p[j] - 1;
index = j;
}
}
// 第三步:截取字符串,输出结果
// 起始索引的计算参看前文第四部分
int start = (index-maxLength)/2;
return s.substring(start, start + maxLength);
}
06 小结
马拉车算法将求解最长回文子串的时间复杂度降低到了O(N)
,虽然也牺牲了部分空间,其空间复杂度为O(N)
,但是其算法的巧妙之处还是值得学习和借鉴的。
算法专题目前已连续日更超过六个月,算法题文章211+篇,公众号对话框回复【数据结构与算法】、【算法】、【数据结构】中的任一关键词,获取系列文章合集。
以上就是全部内容,如果大家有什么好的解法思路、建议或者其他问题,可以下方留言交流,好看、留言、转发就是对我最大的回报和支持!
马拉车算法(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(马拉车算法)
## 背景 该算法用于求字符串的最长回文子串长度. ## 参考文章 >[最长回文子串——Manacher 算法](https://segmentfault.com/a/1190000003914 ...
- 什么是马拉车算法(Manacher's Algorithm)?
提出问题 最长回文子串问题:给定一个字符串,求它的最长回文子串长度. 如果一个字符串正着读和反着读是一样的,那它就是回文串.如a.aa.aba.abba等. 暴力解法 简单粗暴:找到字符串的所有子串, ...
- 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 ...
随机推荐
- [Algorithm] 3. Digit Counts
Description Count the number of k's between 0 and n. k can be 0 - 9. Example if n = 12, k = 1 in [0, ...
- UVA - 247 Calling Circles(Floyd求传递闭包)
题目: 思路: 利用Floyd求传递闭包(mp[i][j] = mp[i][j]||(mp[i][k]&&mp[k][j]);),当mp[i][j]=1&&mp[j][ ...
- Python学习第二阶段Day2,模块subprocess、 logging、re
1.logging 日志开关,设置全局只打印什么级别的日子,默认是warning以下的都不打印 改默认级别:依次升高 logging.debug("") logging.info( ...
- pxc增量备份
###增备数据库,如果后续还需要再次增备,则可以再次指定--extra-lsndir,如果与上次备份指定相同的位置,该文件被覆盖# innobackupex --compress --incremen ...
- LINUX应用开发工程师职位(含答案)
就业模拟测试题-LINUX应用开发工程师职位 本试卷从考试酷examcoo网站导出,文件格式为mht,请用WORD/WPS打开,并另存为doc/docx格式后再使用 试卷编号:143989试卷录入者: ...
- Leetcode 51.N后问题
N后问题 n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 上图为 8 皇后问题的一种解法. 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案. ...
- [luoguP2863] [USACO06JAN]牛的舞会The Cow Prom(Tarjan)
传送门 有向图,找点数大于1的强连通分量个数 ——代码 #include <stack> #include <cstdio> #include <cstring> ...
- noip模拟赛 圆桌游戏
[问题描述] 有一种圆桌游戏是这样进行的:n个人围着圆桌坐成一圈,按顺时针顺序依次标号为1号至n号.对1<i<n的i来说,i号的左边是i+1号,右边是i-1号.1号的右边是n号,n号的左边 ...
- GUI 总结(一)
一/概述 1.两个包: javax.awt //before java 1.2 javax.swing //after java 1.2 2.两个词: 组件Component 容器Container ...
- css3的高级而有用且很少人知道的属性和样式
1.-webkit-mask 概属性可以给一个元素添加蒙层,蒙层可以是一个渐变或者半透明的png图片,这张png图片的 alpha 为 0 的位置会不显示元素这部分,alpha 为 1 的位置会显示元 ...