最长回文子串:Manacher算法[转]

原文:Manacher 算法

背景

给定一个字符串,求出其最长回文子串。例如:

s="abcd";   //最长回文长度为 1
s="ababa";  //最长回文长度为 5
s="abccb";  //最长回文长度为 4,即 bccb

以上问题的传统思路大概是,遍历每一个字符,以该字符为中点向两边查找。其时间复杂度为 O(n2) ,很不高效。而Manacher算法可以把时间复杂度提升到 O(n)

算法过程分析

由于回文分为偶回文(比如bccb)和奇回文(比如bcacb),而在处理奇偶问题上会比较繁琐,所以这里我们使用一个技巧,具体做法是,在字符串首尾,及字符间各插入一个字符(前提这个字符未出现在串里)。

举个例子:s="abbahopxpo",转换为s_new="#a#b#b#a#h#o#p#x#p#o#"。如此,s里起初有一个偶回文abba和一个奇回文opxpo,被转换为#a#b#b#a##o#p#x#p#o#,长度都转换成了奇数。

定义一个辅助数组int p[],其中p[i]表示以i为中心的最长回文的半径,例如:

i0123456789101112131415161718
s_new[i]#a#b#b#a#h#o#p#x#p#
p[i]1212521212121214121

可以看出,p[i] - 1正好是原字符串中最长回文串的长度。

接下来的重点就是求解p数组,如下图:

这里写图片描述

设置两个变量mx,idmx代表以id为中心的最长回文的右边界,也就是mx = id + p[id]。假设我们现在求p[i],也就是以i为中心的最长回文半径,如果i < mx,则p[i]的值可基于以下三种情况得出:

1. j的回文串有一部分在id的之外

这里写图片描述

上图中,黑线为id的回文,i,j关于id对称,红线为j的回文。此时p[i] = mx - i,即紫线。那么p[i]还可以更大么?答案是不可能!见下图:

这里写图片描述

假设右侧新增的紫色部分是p[i]可以增加的部分,那么根据回文的性质,a == d ,也就是说id的回文不仅仅是黑线,而是黑线+两条紫线,矛盾,所以假设不成立,故p[i] = mx - i,不可以再增加一分。

2. j回文串全部在id的内部

这里写图片描述

此时p[i] = p[j],那么p[i]还可以更大么?答案亦是不可能!见下图:

这里写图片描述

假设右侧新增的红色部分是p[i]可以增加的部分,那么根据回文的性质,a == b,也就是说j的回文应该再加上a,b,矛盾,所以假设不成立,故p[i] = p[j],也不可以再增加一分。

3. j回文串左端正好与id的回文串左端重合

这里写图片描述

根据代码,此时p[i] = p[j]p[i] = mx - i,并且p[i]还可以继续增加。

代码实现

#include <string>
#include <vector>
using namespace std;

// Manacher 算法
// 功能:给定一个字符串,求它的最长回文子串长度
int manacher(string str) {
    int max_loc = str.size() * 2;

    // 初始化字符串
    string s_new(max_loc + 1, '#');
    for (int ii = str.size() - 1; ii >= 0; --ii) {
        s_new[2 * ii + 1] = str[ii];
    }

    // 遍历字符串
    vector<int> p(max_loc + 1, 1);
    int max_len = 1;
    for (int ii = 1, jj, id = 0, mx = 1; ii < s_new.size(); ++ii) {
        if (ii - id >= mx) {
            for (; ii + p[ii] < s_new.size() && ii - p[ii] >= 0; ++p[ii]) {
                if (s_new[ii + p[ii]] != s_new[ii - p[ii]])
                    break;
            }
            id = ii;
            mx = p[ii];
            max_len = max(mx, max_len);
        } else {
            jj = id - (ii - id);
            if (jj - p[jj] == id - mx) {
                for (p[ii] = id + mx - ii; ii + p[ii] < s_new.size() && ii - p[ii] >= 0; ++p[ii]) {
                    if (s_new[ii + p[ii]] != s_new[ii - p[ii]])
                        break;
                }
                id = ii;
                mx = p[ii];
                max_len = max(mx, max_len);
            } else if (jj - p[jj] < id - mx) {
                p[ii] = id + mx - ii;
            } else {
                p[ii] = p[jj];
            }
        }
    }
    return max_len - 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值