算法_01_Manacher

来源:互联网 发布:windows资源管理器解锁 编辑:程序博客网 时间:2024/06/08 16:36

问题

求一个字符串最大的回文子串长度
例如:abc12321de,
最大回文子串:12321
返回5

小概念:

  • 子串(连续)
  • 子数组(连续)
  • 子序列(可以连续)

笨办法:

回文分为奇回文和偶回文
奇回文:121
偶回文:1221
针对奇回文,可以从每个位置开始往左右两边扩,即可找到最大回文子串
那偶回文呢?
往里面填充特殊字符,方法:
a121b ——> #a#1#2#1#b#
找到2所在位置,长度7/2=3,即最大回文子串长度为3
a1221b——> #a#1#2#2#1#b#
找到 中间那个#所在位置,两边扩,最大长度9/2=4,即最大回文子串长度为4

注:填充的特殊字符可以是任意的字符,数组中存在的字符也可以,因为他永远是和自己比较的,不会影响计算结果。

总结下:

笨办法就是往里面填充特殊字符后,每个字符都不断去往左右两边扩,找到最长的回文子串,那么如何分析时间复杂度呢?假设,针对这样一个字符串#1#1#1#1#1#1#1#1#每个位置的字符都要扩到边界去,那么1+2+3+…+n/2+(n/2-1)+...+2+1 = O(n^2)说明时间复杂度为 O(n^2)
  1. Manacher

可以将时间复杂度将为 O(n)

首先,有几个概念:
回文范围,回文直径,回文半径
最右回文边界

#1#2#1#用 R 记录最右回文边界用 C 记录第一次取得 R 时的位置

假设已经知道了目前的最右回文边界 R,其所对应的位置为 C,与之相对应的左边界为 L,每个位置的最右回文边界 R 都记录在一个数组pArr[]中。然后我们目前在位置 i 上,有以下两大类,四小类种情况:

  • i位置上的字符在最有回文边界 R的外面(右边),那么直接暴力扩
  • i 位置在R 左边,假设以 C 为中心,与 i 相对应的位置 i’,那么 i’的最右回文必然已经知道了,那么可以根据 i’的最大回文子串的范围和 L 的关系来看:
    • i’的最大回文子串范围在 L 内,那么不用扩,i 位置的最大回文子串长度必然等于 i’的最大回文子串长度
    • i’的最大回文子串范围有一部分在 L 之外,那么也不用扩,i位置的最大回文子串长度等于R-i
    • 刚好压线,此时才需要往左右两边开始扩,从哪里开始扩呢,从 R 位置后面开始。

最后取出数组pArr中最大值

max = Math.max(max, pArr[i]);

那为何又有最有一行呢?

return max - 1;

首先,假设最后答案是 x,填充特殊字符后,最大回文直径为 d=(2*x+1),最大回文半径为r=(d+1)/2=x+1
pArr中记录的就是最大回文半径,所以最后答案x=r-1.

最后附上我上课时的草稿笔记
这里写图片描述

这里写图片描述

这里写图片描述

原创粉丝点击