跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

Manacher 算法详解:求解最长回文子串

Manacher 算法用于高效查找字符串中的最长回文子串。通过预处理将奇偶回文串统一为奇回文串,利用回文对称性和最右回文串边界加速计算,将时间复杂度从 O(n^2) 降低至 O(n)。文中提供了中心扩展法对比及完整的 C++ 代码实现模板。

念念不忘发布于 2026/3/29更新于 2026/10/2582 浏览
Manacher 算法详解:求解最长回文子串

Manacher(马拉车)算法

问题

  1. 在字符串中,找出所有的回文子串;
  2. 在字符串中,找出最长的回文子串。

两个问题可以结合解决。

1. 相关概念引入

1. 回文字符串: 正着读和反着读都一样的字符串就是回文字符串。

2. 回文子串: 一个字符串的某个字串是回文。

3. 奇回文串: 回文串的字符数为奇数。

4. 偶回文串: 回文串的字符数为偶数。

5. 回文中心: c,回文串最中心的位置。奇回文串(回文中心):n + 1 / 2; 偶回文串(回文中心):n / 2 与 n/2 + 1 之间。

6. 回文半径: d,回文中心到回文半径左/右端点的距离(字符数,包括本身)。

2. 中心扩展算法

算法原理

  1. 从前往后遍历字符串,以 s[i] 或 s[i] 与 s[i + 1] 的中间作为回文串的中心位置;
  2. 从中间位置开始,枚举半径长度,逐渐向两边扩展,找出以该点为中心的最长的回文子串。

在这里插入图片描述

预处理

为了防止对奇偶回文字串进行分类讨论,且奇回文字串更好处理,这里将其统一转化为奇回文串。

预处理字符串: 在相邻字符之间和整个字符串的两端任意加入一个字符 '#'。

例如,字符串 s = "abcbaa" 经过预处理之后就变成:s = "#a#b#c#b#a#a#"。

经过预处理之后: 本来是奇回文串,处理之后依旧是奇回文串。例如 "bab" 处理后为 "#b#a#b#"; 本来是偶回文串,处理之后就变成奇回文串。例如 "abba" 处理后为 "#a#b#b#a#"; 此时,在处理之后的串上跑中心扩展算法时,由于所有的回文串都是奇回文串,仅需枚举所有中心点,即可找到所有的回文串。

注意: (不用像 kmp 算法那样,加入一个不会出现的字符,这里可以加入任意字符。因为判断回文的时候,只会原始字符和原始字符判断,新加入的字符和新加入的字符判断。因此,可以加入任意字符。)

代码:

string t, s;
int m, n;
// 以求解最长回文子串为例
int fun() {
    // 预处理字符串
    cin >> t;
    m = t.size();
    s += ' '; // 这里要处理边界不同,' ' != '#'
    for (auto ch : t) {
        s += '#';
        s += ch;
    }
    s += "##";
    n = s.size() - 2;
    int ret = 1;
    // 中心扩展算法
    for (int i = 1; i <= n; i++) {
        int d = 1; // 枚举向右向左的距离
        while (s[i - d] == s[i + d]) d++;
        ret = max(ret, d - 1);
    }
    return ret;
}

时间复杂度:O(n^2)

3. Manacher 算法

概念引入

1. 回文半径数组: d[i] (以 i 为中心的最长回文半径)。 例如,字符串"#a#a#a#b#a#", 回文半径数组:

字符串#a#a#a#b#a#
下标1234567891011
回文半径12343214121

2. 两个重要的性质:

  1. 回文串的长度为 d[i] - 1;
  2. 以 i 为中心的回文串有 d[i] / 2 个。

3. 加速盒子(最右回文串): 从前往后填表的过程中,区间 [l, r], 找到右端点最靠右的回文子串,不断维护区间。 它可以帮助我们加速填表。 如:"#a#a#a#b#a#"; 依次维护的区间:[1, 1] -> [1, 3] -> [1, 5] -> [1, 7] -> [1, 7] -> [1, 7] -> [1, 7] -> [5, 11] -> [5, 11]

4. 【Manacher 算法 - 利用最右回文串加速更新回文半径数组】

分类讨论(核心)

从前往后填表,当填到 d[i] 时,d[1] ~ d[i - 1] 均已经填好,并且维护最右回文串 [l, r]。当填写时,分下面大类,四种情况讨论:

  1. i > r, 当前点没有在最右回文串中。此时,d[1] ~ d[i - 1] 的回文信息提供不了任何帮助。直接以 i 为中心暴力扩展(与中心扩展算法一致);

在这里插入图片描述

  1. i <= r, 当前点在最右回文串中,由对称性可知,j - l = r - i, 对称点 j = r - i + l 的回文半径 d[j], 分为一下三种情况进行讨论:

a. d[j] < r - i + 1(最长回文半径),即以 j 为中心的最长回文串包含在 [l, r] 内: 由对称性可知,d[i] = d[j] = d[r - i + l]

在这里插入图片描述

b. d[j] > r - i + 1,即以 j 为中心的最长回文串的左边界越过了 l: d[i] = r - i + 1。

在这里插入图片描述

c. d[j] = r - i = 1,即以 j 为中心的最长回文串的左边界正好在 l 位置: 此时 d[i] 至少为 d[j], 且还可能往外扩展。就可以从 d[j] 开始,用中心扩展算法暴力向外扩展。

在这里插入图片描述

注意这里:1 和 2.c 情况还会涉及到对最右回文串区间 [l, r] 的更新。

时间复杂度:注意到,在整个算法执行的过程中 r 是不会回退的,相当于 i, r 两个指针不回退的向后移动。 因此整个时间复杂度为 O(n)。

代码实现: 这里非常的精妙,可以把 4 种情况都考虑进去。

string t, s;
int n, d[N];
// 预处理
void init() {
    cin >> t;
    s = ' ';
    for (auto ch : t) {
        s += '#';
        s += ch;
    }
    s += "##";
    n = s.size() - 2;
}
void get_d() {
    d[1] = 1;
    for (int i = 2, l = 1, r = 1; i <= n; i++) {
        int len = r >= i ? min(d[r - i + l], r - i + 1) : 1;
        // =1 是第 1 种情况,d[r - i + 1] 是第 2,r - i + 1 是第 3,两个相等,任取一个是第 4。
        while (s[i + len] == s[i - len]) len++;
        // 1,4 会进入循环,执行中心扩展算法,2,3 会判断不等
        if (i + len - 1 > r) r = i + len - 1, l = i - len + 1;
        // 更新区间
        d[i] = len;
    }
}

在这里插入图片描述

4. 算法模板

代码:

#include<iostream>
using namespace std;
const int N = 2.2e7 + 10;
string t, s;
int m, n;
int d[N];

int main() {
    cin >> t;
    m = t.size();
    s += ' ';
    for (auto ch : t) {
        s += '#';
        s += ch;
    }
    s += "##"; // 处理边界要不同
    n = s.size() - 2;
    d[1] = 1;
    int ret = 1;
    for (int i = 2, l = 1, r = 1; i <= n; i++) // 这里初始化,不能在内 {
        int len = r >= i ? min(d[r - i + l], r - i + 1) : 1;
        while (s[i + len] == s[i - len]) len++;
        if (i + len - 1 > r) r = i + len - 1, l = i - len + 1;
        d[i] = len;
        ret = max(ret, d[i] - 1);
    }
    cout << ret << endl;
    return 0;
}

在这里插入图片描述

目录

  1. Manacher(马拉车)算法
  2. 问题
  3. 1. 相关概念引入
  4. 2. 中心扩展算法
  5. 算法原理
  6. 预处理
  7. 3. Manacher 算法
  8. 概念引入
  9. 分类讨论(核心)
  10. 4. 算法模板

更多推荐文章

查看全部
  • Android 开发者与大模型技术:机遇、挑战与学习路径
  • 国内环境部署 n8n 与私有 AI 模型实战指南
  • 国内大模型公司面试经历与技术复盘
  • Python 3.12 安装库报错:pkgutil.ImpImporter 缺失
  • Python 爬虫实战:Request+urllib 模块批量下载飙歌榜音乐文件
  • 借助 Nano Banana Pro 绘制高质量科研插图的四步法与提示词模板
  • Qwen3-VL 结合 LLaMA-Factory 实现 Grounding 任务 LoRA 微调
  • 前端三年成长记:从理想主义到工程实战的蜕变
  • C++ 哈希表封装实战:unordered_map/set 底层原理与完整实现
  • 基于腾讯云 HAI 与 DeepSeek 快速构建个人网页
  • 多模态大模型垂直微调:基于 Qwen3-VL-4B-Thinking 与 Llama Factory
  • OpenClaw 对接飞书机器人:消息无响应与 Gateway 断开排查
  • Android WebView shouldInterceptRequest 异步加载
  • Android ApplicationInfo 元数据读取 getString 返回 null 问题排查
  • Qwen2.5 技术报告解析:18 万亿 Token 训练与性能表现
  • 网络安全学习路线与技能规划指南
  • Llama-Factory 微调中 Batch Size 的设置与性能调优
  • 文心一言 4.5 开源模型深度解析:轻量化部署与中文优化
  • Android 开发工程师秋招面试真题汇总与备考经验
  • Chaterm 开源 AI 智能终端与 SSH 客户端

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online