Manacher算法操作详解

Manacher

问题引入

  • 求一个长度为 n n n的字符串中回文子串的个数?
  • (或:求一个长度为 n n n的字符串中最长的回文子串的长度?)
  • 回文串:一个正读和反读相同的字符串,称之为回文串。

用已有的知识

  • 算法一:枚举开头,枚举结尾,得到一个串,再扫一遍判断是否为回文串,若是,答案加 1 1 1,时间复杂度 O ( n 3 ) O(n^3) O(n3)
  • 算法二:枚举开头,枚举结尾,得到一个串,用字符串哈希判断其是否为回文串,若是,答案加 1 1 1,时间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 算法三(对下文有用):枚举回文串的对称中心,分长度为奇或偶两种情况,分别向两端延伸,若延伸到的最大长度为 s s s(以奇数的情况为例,最大扩展到 s t i − s + 1 st_{i-s+1} stis+1 s t i + s − 1 st_{i+s-1} sti+s1),那么答案加上 s s s,时间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 枚举回文串的对称中心,分长度为奇或偶两种情况,分别二分能延伸的长度,用字符串哈希判断,若延伸到的最大长度为 s s s(以奇数的情况为例,最大扩展到 s t i − s + 1 . . s t i + s − 1 st_{i-s+1}..st_{i+s-1} stis+1..sti+s1),那么答案加上 s s s,时间复杂度 O ( n log ⁡ 2 n ) O(n\log_2 n) O(nlog2n)

提出疑问

  • 有没有更快的方法,比如 O ( n ) O(n) O(n)
  • 有没有更简洁的方法,不用哈希?
  • 答案是:有!

算法介绍

  • 算法名称:Manacher’s Algorithm
  • 主要思路是用已经计算出的回文子串,利用它回文的特性,对即将计算的回文子串提供一定的帮助。
  • 需要记录 l , r l,r l,r表示已经找出的回文子串中,右端点最靠右的回文子串所在区间,
  • 初始值分别为 0 , − 1 0,-1 0,1
  • 以下以长度为奇数的情况为例,介绍算法操作流程。
  • 1 − n 1-n 1n依次枚举字符串的对称中心,对于每个 i i i,考虑向两边扩展,扩展的最长长度为 d i d_i di(也就是最大扩展到 s t i − d i + 1 . . s t i + d i − 1 st_{i-d_i+1}..st_{i+d_i-1} stidi+1..sti+di1)。
  • 定义朴素算法为上文“用已有的知识”中的算法三,
  • 那么我们考虑优化这个朴素算法,算法的核心是利用已有的 d d d来加速计算当前的 d i d_i di
  • 分两种情况:
  • 一、 i > r i>r i>r(若长度为偶数则 i ≥ r i≥r ir
  • 直接调用朴素算法计算 d i d_i di
  • 二、 i ≤ r i≤r ir(若长度为偶数则 i < r ii<r
  • 即说明 i i i在回文子串 s t l . . s t r st_l..st_r stl..str内,那么找到 i i i在这个回文子串中对称的点 j j j
    j = l + r − i j=l+r-i j=l+ri
  • 因为对称,所以巧妙地利用这个性质, d i = d j d_i=d_j di=dj
  • 思考:这样是正确的吗??
  • 其实不然——
  • 因为以为中心 j j j的回文子串,左边界可能超出 l l l,那么对称过去到了 i i i就不在大回文子串 s t l . . s t r st_l..st_r stl..str内了,不能保证也回文,所以
    d i = m i n ( d j , r − i + 1 ) d_i=min(d_j,r-i+1) di=min(dj,ri+1)
  • 同理,对 i i i而言,以它为中心的实际最大回文子串可能右边界超出 r r r,那么 d j d_j dj是计算不到的,所以,在上式的基础上,再次调用朴素算法。
  • 至此,这个问题完美解决。

时间复杂度分析

  • 算法是两重循环,看似复杂度极限会是 O ( n 2 ) O(n^2) O(n2)???
  • 其实并不如此,实际上,它的复杂度十分优秀。
  • 观察调用朴素算法的条件,发现每次朴素算法的调用,只会判断还没有被回文子串覆盖过的字符,也就是说,每个字符只会被朴素算法进行一次,
  • 这样下来,时间复杂度是均摊 O ( n ) O(n) O(n)的。

你可能感兴趣的:(字符串)