Manacher's Algorithm 马拉车算法

算法课的作业,每个组需要讲解一个经典问题和解决方案,同组的同学想讲这个,发给了我一些资料,此文仅作自己的理解过程的一个记录,如有错误之处请指教。

问题

经典的最长回文子串问题(Longest_palindromic_substring)。回文串就是正读反读都一样的字符串,比如 “a”,“bob”, “noon” 等。最长回文子串问题即在一个字符串中找出其长度最大的回文子串(这不废话嘛)。

传统解决方案

那么如何找呢,当然可以遍历每个字符,分别以其为中心,向两边寻找最长回文子串,遍历完整个数组后,就可以找到最长的回文子串。但是这个方法的时间复杂度为O(n2),这简直是乌龟拉车,贼慢。

那马拉车呢?

这个算法一共有以下几步。

积偶合并

如上文,"bob"和"noon"都是回文字符,显然需要分情况讨论,而马拉车算法的第一步就是用一种添加字符的方法,在每一个字符的左右都添加上#号,把两种情况合并了。举例如下:
bob ——> #b#o#b#
noon ——> #n#o#o#n#
这样,添加字符后整个字符串一定是奇数(不信你自己查下)

我们需要用一个半径数组p[i]来 找长度 和 找位置

  • 首先,在用#号处理字符前,如果知道了分别以每个字符为中心的最长回文串的半径,就可以得到最大的半径,从而可以得到最后的最长回文子串长度了。即这个半径与回文子串长度是有对应关系的。那么对按照固定方法(有规律的加#号)处理后的字符串,同样求出每个字符的新半径时,显然也可以对应到真实的回文子串长度。举个例子:
    “bob"中p[0] (以b为中心) 是1,p[1] (以o为中心)是2,p[2] (以b为中心) 是1.
    所以以b为中心的最长子串就是21-1=1,以o为中心的最长子串是22-1=3,以第二个b为中心的最长子串也是2*1-1=1。
    这就是对应关系。
    而对处理后的字符串”#b#o#b#",其对应的p[]就是{1,2,1,4,1,2,1}
    那新的对应关系呢?我们来看,#号实际不存在,所以以其为中心的真实的回文子串长度是0,所以每个字符的最长回文子串长度数组应该是{0,1,0,3,0,1,0},做下对比,其实就是p里面的数全部减了1,其实数学上很容易证明,也很容易想明白,前面是用半径对应的,现在的半径里相当于加了半径的#号个数直接变成长度了,又算了两遍自己什么的,好好想想很容易想明白。
  • 光找到长度不行啊,还得知道从哪开始的啊,就是定位,定位才能把最后的最长回文子串找到。这里处理前的规则特别简单,位置i的字符半径如果是r,那i-r+1就是开始的字符的位置。处理后自然也有一个对应规则,不过这个规则经过了一些处理,具体规则肯定是经过马拉车先生自己试出来的,想了解过程的我给你们复制下来。。其实就是试。

我们还是先来看中间的 ‘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。 感受感受,其实也很容易理解,加#号相当与加了一倍(还少1),加了个 $ 号相当于刚好补成两倍长,剪了新半径相当于减了自己的长度,然后再放缩(缩)回1/2就是起始位置了。

怎么把这个p[i]搞出来?

暴力循环?那不还是之前的方法,得想个好办法啊,怎么想呢?我怎么知道,马拉车想出来的,我看完只想说,果然聪明。。
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
what’s this?
在这里插入图片描述
what’s 又 this?

刚开始我也看的很晕,别急。
首先,搞清楚我们要干啥,求p[i]!
mx又是什么? 离i最近的回文子串的右端
那id是什么? 上面说的那个最近的子串的中心
请注意,不需要它长,只需要它近!当然又近又长最好,但是近优先度高于长。
至于原因也很简单,就是要用这个子串内部的对称方便求p[i],那肯定是离i越近的子串越好啊。
哪来的j??? 创造的啊!,j=2*id-i ,那(i+j)/2不就是id么!i关于id的对称点懂么?通过对称的特性来简化求p[i]啊!
心累,那这个表达式又又又表达了什么逻辑?? 利用id、mx、j 简化求p[i]啊
首先问mx大于i么?如果mx大于i&#x

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值