马拉车算法(Manacher Algorithm)--用于计算最长回文子串

马拉车算法的目标是找到一串字符串中的最长回文子串,优点是时间复杂度为O(n)
现以寻找 “cgbaabgk” 中的最长子回文串( “gbaabg”)为例进行说明

算法过程(总共3步):

1.改造字符串结构:

字符坐标 0 1 2 3 4 5 6 7 8 9 10 11 12 13
char $ # g # b # a # a # b # k #

将原字符串"c g b a a b g k"改造成 "$ # c # g # b # a # a # b # g # k # "
改造的原则是,先在每个字母之间插入’#‘号,再在最前面加入’$'符号
(先不用管为什么这么改造,多做几次就能领悟了)


2.计算新字符串中,以每个字符为中心的子回文串半径:
在这里插入图片描述

例如:
‘$’ 为中心的子回文串就是 " $ “它自己一个字符,那么回文串半径是1;
‘g’ 为中心的子回文串是”#g#",长度是3,所以以字符’g’为中心的回文串半径为2
坐标为7的’#'号 为中心的回文串为"#b#a#a#b#",长度为9,回文串的半径是"#a#b#",长度是5;
这里的回文串半径不是单纯的回文串长度除以2,而是从回文串中心到回文串最右边的字符的长度。


3.通过步骤2的表格获得我们想要的数据:
如果我想知道原来的整个字符串"gbaabk"中,最长的回文串的长度是多少,那就是表格中radius最大数减一:
在这里插入图片描述
即5-1=4,这个字符串里面的最长回文串长度为4

如果我想求出这个字符串里面的最长回文子串,可以得到这个子串的起点和终点坐标:
在这里插入图片描述
最长子回文串为"baab":
起点字符’b’在原字符串中的坐标:(7-radius[7])/2=(7-5)/2=1
终点字符’b’在原字符串中的坐标:(7+radius[7]-1)/2-1=(7+5-2)/2-1=4
通过这两个坐标可以得到原字符串中的最长回文子串"baab"


马拉车算法就这三步,那么如何高效地获得这个radius一栏的值呢?
在这里插入图片描述

如何获得改造后的字符串的回文半径:

以上面的字符串“$#g#b#a#a#b#k#”为例,马拉车算法是从左到右,依次计算各字符的回文半径。但是这里计算不是蛮力法,而是有技巧的。


这里有三种情况需要讨论:
1.当我们已经从左到右计算到7号’#'号的回文半径为5时:

在这里插入图片描述
由对称性可知,方框1和方框2中的内容是关于7号’#'字符对称的,所以8号’a’是关于6号’a’对称的6号’a’的回文半径是2,故8号’a’的回文半径也是2。所以我们可以直接认为8号’a’的回文半径就是2了,而不用继续蛮力求它的回文半径长度。

2.当我们想求’b’的回文半径时,是否也能直接认为它的回文半径是4号’b’的回文半径2呢?不能。因为可以看到,坐标4的’b’的势力范围已经接触到7号’#'的最大势力范围了,我们没有办法保证10号’b’是否会有更大的回文半径
比如这里如果12号如果不是’k’而是’a’的话,那’b’的回文半径就不是2,是4(回文字符串"#a#b#a#“的回文半径是4)。
在这里插入图片描述
所以我们先假设10号’b’的回文半径是2,,然后继续用蛮力法探究一下10号’b’的回文半径到底能扩散到哪里。
可以看到以10号坐标’b’为中心的的回文串为”#b#",故回文半径为2.
在这里插入图片描述


然后开始计算坐标11’#‘的回文半径:
此时11号’#'已经在7号‘#’的势力范围之外了,所以不能再借助7号’#‘的回文半径来估算11号’#‘的回文半径了,只能老老实实的蛮力算。
在这里插入图片描述
可以算出以11号’#‘为中心的回文串为"#"(即就它一个),所以11号’#'的回文半径是1.
这样一个一个算下去就可以得到以每个字符为中心的回文半径:
在这里插入图片描述
得到一行回文半径后,根据第三步的内容就可以得到我们想要的结果(最长半径是多少、最长回文子串是什么)

猜你喜欢

转载自blog.csdn.net/AXIMI/article/details/84983152
今日推荐