manacher算法

manacher算法#

manacher(马拉车)算法是用于快速查找最长回文子串的算法。
基本思想是在遍历查找的基础上维护一个当前回文字符串的位置,用于加速后续的查找。
记,left表示当前回文字符串的左边界;right表示当前回文字符串的右边界;idx表示当前索引的位置;mirror表示索引在回文字符串中关于中心的镜像点。后面的当前记录的回文字符串使用[left,right]表示。
那么可能会出现以下几种情况:

  1. idx超出了[left,right]范围,也就是idx > right,这时没有更好的办法,只能遍历查找以当前位置为中心的最长回字符串,然后更新[left,right]
  2. idx落在[left,right]范围内,那么去查看mirror的状态,因为mirror肯定在idx左侧的,也就是说在搜索idx位置的回文字符串时,mirror一定是被搜索和计算过了的。又因为落在了[left,right]中,意味着mirror+1位置和idx-1位置的元素相同;如果mirror-1位置还落在区间中,那么mirror-1位置和idx+1位置的元素也相同,可以得到这么一条结论:在[left,right]区间内,idx位置对应的回文字符串,至少拥有和mirror相同的长度。这时会再出现两种情况:
    2.1. mirror位置的回文字符串落在[left,right]范围内(不包含压线的情况),那么idx位置的回文字符串也不会超出范围,且和mirror位置的长度相同,可以使用反证法来验证——mirror回文字符串半径记作radius,可以知道mirror+radius+1肯定和mirror-radius-1处的元素不同,假设idx位置的回文字符串长度大于mirror,则idx+radius+1处的元素一定和idx-radius-1处的相同,又因为落在[left,right]范围内,则idx+radius+1mirror-radius-1处的元素相同,且idx-radius-1mirror+radius+1处的元素相同,这与已知的矛盾。
    2.2. mirror位置的回文字符串超出了[left,right]范围(包含压线的情况),那么可以说,idx至少到right位置都是回文的,只需要从right再向后遍历,并更新[left,right]即可。
std::string manacher(std::string_view s) {
    std::string tmp;
        // 将字符串扩展成奇数 abcd -> #a#b#c#d#...
        // 使得原字符串无论奇数还是偶数长度,都会变成奇数长度
        for(int i = 0; i < static_cast<int>(s.size()); i++) {
            tmp.push_back('#');
            tmp.push_back(s[i]);
        }
        tmp.push_back('#');
        // 定义回文字符串的范围,定义了当前的回文字符串的两端
        int left = 0, right = 0;
        // 定义了字符串中每个位置作为中心时的最大回文字符串半径
        std::vector<int> radius(tmp.size(), 0);
        for(int idx = 0; idx < static_cast<int>(tmp.size()); idx++) {
            // 当搜索的位置超出了当前回文字符串的范围时,只能向后遍历
            if(idx > right) {
                int l = idx, r = idx;
                while(l >= 0 && r < static_cast<int>(tmp.size()) &&
                      tmp[l] == tmp[r]) {
                    --l;
                    ++r;
                }
                radius[idx] = (r - l - 2) / 2;
                left = l + 1;
                right = r - 1;
            }
            else {  // 如果当前搜索的位置在当前的回文字符串范围中
                // 搜索位置在当前回文字符串中关于中心的镜像点对应的回文字符串不超出当前回文字符串,说明当前搜索位置的回文字符串半径也只能这么长
                int center= left + (right - left) / 2;
                int mirror = center- (idx - center);
                if(radius[mirror] < mirror - left) {
                    radius[idx] = radius[mirror];
                }
                else {  // 如果镜像点的长度超出了当前回文字符串,则当前搜索位置还需要继续向后遍历
                    int l = idx - (mirror - left), r = idx + (mirror - left);
                    while(l >= 0 && r < static_cast<int>(tmp.size()) &&
                          tmp[l] == tmp[r]) {
                        --l;
                        ++r;
                    }
                    radius[idx] = (r - l - 2) / 2;
                    left = l + 1;
                    right = r - 1;
                }
            }
        }
        // 找到最长的回文字符串
        int pos = 0;
        for(int i = 0; i < static_cast<int>(radius.size()); i++) {
            pos = radius[i] > radius[pos] ? i : pos;
        }
        // 此时,pos对应了扩展后的字符串中最长回文字符串的中心,通过它再映射回原字符串
        std::string res;
        for(int i = pos - radius[pos]; i <= pos + radius[pos]; i++) {
            if(tmp[i] == '#') {
                continue;
            }
            res.push_back(tmp[i]);
        }
        return res;
}

原字符串长度可能是奇数或者偶数,可以使用技巧——相邻元素间插入不可能出现的字符,将其都变成奇数长度的字符串,这样便于统一处理。不使用技巧的话也能处理,只不过就需要分奇偶长度了,但过程差不多。

作者:cwtxx

出处:https://www.cnblogs.com/cwtxx/p/18718172

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   cwtxx  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· Apache Tomcat RCE漏洞复现(CVE-2025-24813)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示