8.3随笔:Manacher算法

Leetcode 647. 回文子串的个数

起由是刷代码随想录的DP,做到647问题,结果用DP算法时间上只打败了55.8%,想到可能有复杂度更低的算法。

由于DP和中心拓展都是O(n^2)的算法,而回文串至少是要遍历一遍的,因此,最优解应该是线性或者nlogn的时间复杂度的——即Manacher算法,又被戏称为马拉车算法。

马拉车算法可以看作是中心拓展的一种改良。

对于字符串abbaa,若用中心拓展,那有两种中心选择方式

        1. 以单字符为中心

        2. 以双字符为中心

但是马拉车算法首先优化了中心选择的方式,向字符串相邻空间插入"#"将所有字符串长度变为奇数个以单字符为中心。 

Manacher 算法的几点说明

  1. 记f(i) 为 s 的第 i 位的回文半径长度,那么容易证明 f(i)-1就是以下标 i 为中心的最大回文子串长度(这个证明考虑插入的#个数就可以想明白了)
  2. 那么如果计算 f(i) 那么就说明[1, i-1] 区间范围内都已经计算过了,那么如果在[1,i-1]中有一个点的半径长度包括到了 i ,那么我们就可以利用上i 关于这个半径的中点的对称点的相关回文信息,因此我们就需要维护一个最长右端点和达到该右端点的中心点。
  3. 按找上面的思路,那如果 i 都不在最长右端点,那也就是说我们利用不到之前回文的信息,那就直接以 i 为中心暴力中心拓展咯

这里是该题相应的代码,前面在初始化字符串的时候加上了 $ , 不然后面求 f(i) 的时候会出现问题

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size();
        string t = "$#";
        for (const char &c: s) {
            t += c;
            t += '#';
        }
        n = t.size();
        t += '!';

        auto f = vector <int> (n);
        int iMax = 0, rMax = 0, ans = 0;
        for (int i = 1; i < n; ++i) {
            // 初始化 f[i]
            // rMax - i + 1是他到边的长度,f[2 * iMax - i]是他对称点的最长回文子串长度;
            // 由于只遍历到[1,i-1]于是在i后面的情况不能确定,因此要始终保证在最长边范围内
            // 于是我们取小;
            f[i] = (i <= rMax) ? min(rMax - i + 1, f[2 * iMax - i]) : 1;
            // 中心拓展
            while (t[i + f[i]] == t[i - f[i]]) ++f[i];
            // 动态维护 iMax 和 rMax
            if (i + f[i] - 1 > rMax) {
                iMax = i;
                rMax = i + f[i] - 1;
            }
            // 统计答案, 当前贡献为 (f[i] - 1) / 2 上取整
            ans += (f[i] / 2);
        }

        return ans;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

szfmsmdx

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

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

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

打赏作者

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

抵扣说明:

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

余额充值