Manacher(马拉车)算法详解

更多算法详解见:gitee

【模板】manacher 算法

回文串定义:即正着看反着看都一样的字符串,如 a b a 、 a a a 、 c a c aba、aaa、cac abaaaacac,回文串可以分为奇回文串和偶回文串, a b b a abba abba就是一个偶回文串, a b a aba aba就是一个奇回文串

求最长回文串的其他思路

求最长回文子串的方法,最容易想到的就是暴力枚举,将所有的回文子串都找出来,然后找做大的,时间复杂度就相当高了 O ( n 3 ) O(n^3) O(n3)

还有比较容易想到的就是区间 d p dp dp,想法就是 S [ i ] S[i] S[i] S [ j ] S[j] S[j]是回文串的前提是 S [ i + 1 ] S[i+1] S[i+1] s [ j − 1 ] s[j-1] s[j1]是回文串,是区间 d p dp dp时间复杂度就是 O ( n 2 ) O(n^2) O(n2),对于暴力枚举来说有所优化。

还有另一种枚举的方法,是中心扩散法,利用了回文串的对称性,枚举每一个字符,然后从中心向两边比较,时间复杂度也为 O ( n 2 ) O(n^2) O(n2)

今天介绍的这个算法可以将时间复杂度降到线性的 O ( n ) O(n) O(n)

算法思路

马拉车算法充分利用了回文串的对称性和之前遍历过的区间。算法的主要思路是维护一个最右回文子串,利用该区间的对称性,计算当前遍历位置为中心的回文半径,回文半径就是从中心(如果为奇回文串,就包含中心位置)到回文串一端的距离。

我们设有字符串 S S S S [ l … r ] S[l\dots r] S[lr]为串 S S S中区间为 [ l , r ] [l,r] [l,r]的子串, S [ i ] S[i] S[i]表示串 S S S中第 i i i个字符,我们用 d [ i ] d[i] d[i]表示以第 i i i个字符为中心的回文半径。为了便于理解我们这里先只考虑奇回文串。

我们现在维护一个最右回文子串 S [ l … r ] S[l\dots r] S[lr],设该回文串的中心是 S [ m i d ] S[mid] S[mid],设这个回文子串的回文半径为 d [ m i d ] d[mid] d[mid]。那么我们要求的当前位置的回文半径 d [ i ] d[i] d[i]就要有下的几种情况

i i i [ l , r ] [l,r] [l,r]的范围内

我们假设 j j j i i i关于 m i d mid mid的对称点,对应 j j j又有三种情况

  • j j j为中心的回文子串在 [ l , r ] [l,r] [l,r]

    根据回文串的对称性,我们很容易就可以知道 d [ i ] = d [ j ] d[i]=d[j] d[i]=d[j]
  • j j j为中心回文子串有部分在最右回文子串的外面

    显而易见的, j − l = r − i = c j-l=r-i=c jl=ri=c [ j − c , j + c ] [j-c,j+c] [jc,j+c] 可以由 m i d mid mid对称到 i i i,所以 [ i − c , i + c ] [i-c,i+c] [ic,i+c]是回文子串,那么以 i i i为中心的回文半径还可以继续增加吗?答案是不行,假如我们让 i i i的回文半径再增加 1 1 1

    图中绿色的方块都是通过对称性得到的相等的字符,但是最右和最左的两个绿色方块一定是不相等的,因为如果相等 d [ m i d ] d[mid] d[mid]也会增加,从而使得最右回文子串包含他们,但是这里并没有包含,所以最左和最右的两个字符一定不相等,与对称性得到的结论相悖,所以以 i i i为中心的回文半径就是 r − i + 1 r-i+1 ri+1
  • j j j为中心的回文子串的左边界和最右回文子串重合

    这种情况我也可以很容易的看出 d [ i ] ≥ d [ j ] d[i]\ge d[j] d[i]d[j]的,这时 d [ i ] d[i] d[i]是可以继续增加的,所以我们就要用中心扩散法来求得 d [ i ] d[i] d[i]
i i i [ l , r ] [l,r] [l,r]


这时我们不能利用对称性来判断回文半径,就要用中心扩散法来求 d [ i ] d[i] d[i]的值。

算法运行过程种,如果有比 [ l , r ] [l,r] [l,r]更靠右的回文子串区间,就要更新最右回文子串

根据上面的分析,我们很容易可以看出:
i i i [ l , r ] [l,r] [l,r]内时 d [ i ] = m i n ( d [ j ] , r − i + 1 ) d[i]=min(d[j],r-i+1) d[i]=min(d[j],ri+1),但是当边界重合时,还是要中心扩散法求 d [ i ] d[i] d[i]
i i i [ l , r ] [l,r] [l,r]外时要中心扩散法求 d [ i ] d[i] d[i]

处理奇偶回文串

上面的方法只适用于判断奇回文串,因为偶回文串没有中心字符,就不适用了。我们可以再每个字符之间加入特殊字符,奇回文串的中心不变,偶回文串的中心就变成了特殊字符。

我们还可以在字符首尾加上两个不同的特殊字符,这样边界也不用考虑了。
e g : a b c b a ⟶ @ # a # b # c # b # a # ! eg:abcba \longrightarrow @\#a\#b\#c\#b\#a\#! egabcba@#a#b#c#b#a#!

C o d e Code Code

实现时我们用 m i d mid mid最右回文子串的中心和 r r r最右回文子串的右端点,来维护最右回文子串区间

int Manacher(string& s) {
    vector<int> d(s.size() * 2 + 3);
    //初始化字符串
    string str("@");
    for (char ch : s)
        str += "#",str+=ch;
    str += "#$";
    //我们用mid,r来维护最右回文子串
    //len是最长回文子串的长度
    int r = 0, n = str.size(), len = 0, mid = 0;
    for (int i = 1; i < n - 1; ++i) {

        //判断i是否在最右回文区间内
        if (i <= r)d[i] = min(d[(mid << 1) - i], r - i + 1);
        else d[i] = 1;

        //中心扩散法求d[i]
        //这样写是为了追求代码简洁,因为其他情况并不会进入循环,不影响时间复杂度
        //就不写额外的判断来区分情况了
        while (str[i + d[i]] == str[i - d[i]])++d[i];

        //更新最右回文子串
        if (i + d[i] - 1 > r)mid = i, r = i + d[i] - 1;

        //更新最长回文子串的长度
        len = max(len, d[i] - 1);
    }
    return len;
}
  • 12
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
马拉车算法是一种常用于字符串匹配的算法,其核心思想是利用回文串的对称性来减少比较次数。Java中可以通过以下步骤实现马拉车算法: 1. 预处理字符串,将字符串中的每个字符用一个特殊字符隔开,如:将字符串"abc"变成"#a#b#c#" 2. 维护一个数组P,其中P[i]表示以i为中心的最长回文子串的半径长度。具体实现时,可以使用一个中心点center和右边界right来维护,其中center表示当前已知的最长回文子串的中心点,right表示该回文子串的右边界。根据回文串的对称性,可以利用已知回文串的左侧字符的对称点来推出右侧字符的回文半径。 3. 遍历字符串,根据P数组更新center和right,并记录最长回文子串的起始位置和长度。 以下是Java代码实现示例: ```java public class ManacherAlgorithm { public static String longestPalindrome(String s) { if (s == null || s.length() == 0) { return ""; } StringBuilder sb = new StringBuilder(); sb.append("#"); for (int i = 0; i < s.length(); i++) { sb.append(s.charAt(i)); sb.append("#"); } String str = sb.toString(); int[] P = new int[str.length()]; int center = 0, right = 0; int start = 0, maxLen = 0; for (int i = 0; i < str.length(); i++) { if (i < right) { P[i] = Math.min(right - i, P[2 * center - i]); } while (i - P[i] - 1 >= 0 && i + P[i] + 1 < str.length() && str.charAt(i - P[i] - 1) == str.charAt(i + P[i] + 1)) { P[i]++; } if (i + P[i] > right) { center = i; right = i + P[i]; } if (P[i] > maxLen) { start = (i - P[i]) / 2; maxLen = P[i]; } } return s.substring(start, start + maxLen); } public static void main(String[] args) { String s = "babad"; System.out.println(longestPalindrome(s)); } } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IdlePerson.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值