高效 遍历 算法_基础算法——马拉车算法Manacher’s Algorithm

a2c4b5384146b647a9fb09a37309c43e.png

最近一直想记录下自己学了啥,写文章是个好办法。一方面锻炼自己表述能力,另一方面加强理解。最重要的很多东西看了也是一知半解,希望能得到更多批评指正。


求字符串最大回文子串问题,一般是给出字符串,求给出子串中左右对称的最长子字符串。

例如"dabcba",的回文字符串就是"abcba"。

其解决办法除了,硬怼和动态规划外,还有Manacher算法(马拉车算法)。

马拉车算法时间复杂度一般说是O(n),空间复杂度O(n),是非常高效的一个算法。

其思路如下:

  1. 调整字符串

因为回文子串有两种可能"aba"和"bb",如果直接处理需要判断子串中心是否有字符。而马拉车算法先为字符串填充无效字符,例如"#"。这样上述字符串就变成"#a#b#a#"和"#b#b#"。这样无论原字符串怎样,新生成的字符串都是长度为奇数,中心有字符。

2. 判断字符半径

这里先引入一些概念和变量。

字符半径:就是以该字符为中心可以形成的最大回文字符串的半径。比如"#a#b#a#"的半径为3。

节点 i :被遍历节点i。

节点maxR : 容纳节点i最大回文子串所覆盖的最大位置。

节点pos : 容纳节点i最大回文子串的中心位置。

节点j : 以pos为中心,节点i的对称位置。

数组R : 记录所有节点为中心的最大回文半径。

之后节点i从左至右遍历字符串,首先预估节点i最小半径,不断扩大搜索范围以确定最终半径。数组R记录下来。最终有了全部回文子串的中心和半径就能确定算法就可以解决。

3. 如何确定节点i的最小半径

如果所有节点i的半径都从0开始枚举,算法复杂度太高。因为在遍历节点i之前,数组R已经记录了过去节点的回文信息。通过maxR和pos记录容纳节点i最大回文子串信息。因为回文子串的左右必然对称,可以估计节点i的半径最小在maxR-i 和其对称节点j半径之间。如图:

d75e19564cadce6ada3e98ba19e7234f.png

公式:

2a63b03bcf32ad51df3cae5130508d38.png

当节点i探索范围超过maxR,则替换maxR和pos。以此遍历完整个字符串后,选择最大子串,保留在奇数位原字符串字符即可。

//
// Created by timruning on 19-4-14.
//

#include <iostream>
#include <string>
#include <vector>

using namespace std;

string manacherAlg(string &x) {
    string a = "#";
    for (char v:x) {
        a.push_back(v);
        a.append("#");
    }
    int pos = 0;
    int maxR = 0;
    vector<int> R(a.size(), 0);
    for (int i = 0; i < a.size(); i++) {
        R[i] = maxR > i ? min(maxR - i, R[2 * pos - i]) : 0;
        while (R[i] + i < a.size() && i >= R[i] && a[i - R[i]] == a[i + R[i]]) {
            R[i] += 1;
        }
        if (R[i] + i > maxR) {
            maxR = R[i] + i;
            pos = i;
        }
    }
    int sub[] = {0, 0};
    for (int i = 0; i < a.size(); i++) {
        if (R[i] > sub[1]) {
            sub[0] = i;
            sub[1] = R[i];
        }
    }
    string sub2 = "";
    for (int i = sub[0] - sub[1] + 1; i <= sub[0] + sub[1] - 1; i++) {
        if (i % 2 == 1) {
            sub2 += a[i];
        }
    }
    return sub2;
}

int main() {
    string x = "waabwswfd";
    string result = manacherAlg(x);
    cout << result << endl;

}

本文借鉴了 http://www.cnblogs.com/grandyang/p/4475985.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值