Manacher's Algorithm 马拉车算法

最长回文子串问题,网上看O(n)的算法都是马拉车算法,但是很多讲的不够透彻。找了一圈,这篇是我唯一完全看懂了的:

Manacher’s Algorithm Explained— Longest Palindromic Substring

 

下面按照自己的理解,阐述一遍,算是巩固吧。

一、处理字符串长度有奇有偶的问题

对原字符串做预处理,每两个字符中间的空位,插入一种特殊字符,例如#。另外,头尾也插入这种特殊字符。

原来奇数长度的串,如aba,插入后#a#b#a#

原来偶数长度的串,如acca,插入后#a#c#c#a#

原字符串长n,插入了n+1个#,总长2n+1,恒为奇数,这样就解决了奇偶需要分类处理的问题。

二、回文串中的镜像概念

如上图示,c为回文串中心,l为其左边界,r为右边界。

此时称以c为中心的回文子串的回文半径为r-c

由回文串的镜像性质,可知每一个在[c,r]区间内的点j,都会有一个镜像点j',落在[l,c]区间内。

 

三、建立辅助数组P[]

P[i]记录以i为中心的最长回文子串的回文半径。

设点i在[c,r]区间内,其镜像点i' = 2*c - i。因为P[]的计算是从前到后的,此时的P[i']已计算完成。

根据以i'为中心的回文串的左边界是否超过l,分两种情况讨论:

1)以i'为中心的回文串左边界没有超过l,则P[i]的最小可能值是P[i']。

2)以i'为中心的回文串左边界超过了l,则P[i]的最小可能值是r-i。

这里用反证法说明:如果以i'为中心的回文串左边界超过了l,而r+1位依然以i为中心形成回文的话,那么根据镜像性,第l-1位将以i'为中心形成回文,且满足str[l-1] == str[r+1]。如此则以c为中心的最长回文串的区间应该扩大为[l-1, r+1],与假设的[l,r]相矛盾,故不可能。

string manacher(string s) {
        const int n = s.size();
        if (!n)
            return s;
        
        string ss = "#";
        for (int i = 0; i < n; ++i) {
            ss += s[i];
            ss += '#';
        }

        const int N = ss.size();
        vector<int> P(N, 0);
        int c = 0, l = 0, r = 0;
        int maxLen = 0;
        for (int i = 1; i < N; ++i) {
            // ii is the mirror of i with the center c
            int ii = 2 * c - i;
            if (i < r) {
                P[i] = min(ii >= 0 ? P[ii] : 0, r - i);
            }
            else {
                P[i] = 0;
            }

            //il, ir is the bourndary of the longest panlindrome substr centered at i
            int il = i - P[i] - 1, ir = i + P[i] + 1;
            while (il >= 0 && ir < N && ss[il] == ss[ir]) {
                ++P[i];
                --il;
                ++ir;
            }

            // length of the longest palindrome substr centered at i
            int len = 2 * P[i] + 1;
            if (len > maxLen) {
                maxLen = len;
                c = i;
                l = c - P[i], r = c + P[i];
            }
        }

        if (ss[l] == '#')
            ++l;
        if (ss[r] == '#')
            --r;
        // char at i in ss is at i/2 in s
        int left = l / 2, right = r / 2;

        return s.substr(left, right - left + 1);
    }

LeetCode测试通过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值