马拉车算法

news/2025/3/4 13:31:25/文章来源:https://www.cnblogs.com/yythm/p/18750348

马拉车算法

马拉车(Manacher's Algorithm)是一种用于 求解最长回文子串 的线性时间复杂度 O(n)O(n) 算法。

📌 核心思想

  1. 字符串预处理:在原字符串中插入特殊字符 #,避免奇偶回文的区分。
  2. 回文半径数组 P[]P[i] 表示以 i 为中心的最长回文子串的半径。
  3. 回文扩展:使用 回文对称性 以及 已知信息 快速跳过不必要的计算,保持线性复杂度。
  4. 动态中心更新:维护 centerright,减少重复计算。

🚀 完整代码

public class Manacher {public static String longestPalindrome(String s) {if (s == null || s.length() == 0) {return "";}// 预处理字符串:在字符之间插入 '#'String t = preprocess(s);int n = t.length();int[] P = new int[n];  // 记录回文半径int center = 0, right = 0;  // 当前回文中心和最右扩展边界int maxLen = 0, start = 0;  // 记录最长回文的起始索引for (int i = 0; i < n; i++) {// 计算 P[i] 的初始值int mirror = 2 * center - i; // i 关于 center 的对称点if (i < right) {P[i] = Math.min(right - i, P[mirror]); // 利用对称性减少扩展}// 尝试中心扩展while (i + P[i] + 1 < n && i - P[i] - 1 >= 0 &&t.charAt(i + P[i] + 1) == t.charAt(i - P[i] - 1)) {P[i]++;}// 更新 center 和 rightif (i + P[i] > right) {center = i;right = i + P[i];}// 记录最长回文信息if (P[i] > maxLen) {maxLen = P[i];start = (i - maxLen) / 2; // 计算原字符串中的索引}}// 提取最长回文子串return s.substring(start, start + maxLen);}// 预处理:在字符之间插入 '#'private static String preprocess(String s) {StringBuilder sb = new StringBuilder();sb.append('#');for (char c : s.toCharArray()) {sb.append(c).append('#');}return sb.toString();}// 测试public static void main(String[] args) {String s = "babad";System.out.println("最长回文子串: " + longestPalindrome(s)); // 可能输出 "bab" 或 "aba"}
}

🔍 代码解析

  1. 字符串预处理
    • 在每个字符之间插入 #,将所有子串都转换为 奇数长度
    • 例如,"babad""#b#a#b#a#d#"
  2. 回文半径数组 P[]
    • P[i] 存储 i 为中心的最大回文扩展半径
    • right 记录当前已扩展的最右端,center 记录当前回文中心
    • iright 之内,则可以 利用对称点 mirror = 2 * center - iP[mirror] 直接赋值,减少计算。
  3. 回文扩展
    • 如果 P[i] 初值已经达到边界,尝试进一步扩展
    • t.charAt(i + P[i] + 1) == t.charAt(i - P[i] - 1),向两侧扩展。
  4. 更新回文中心 center 和最右边界 right
    • i + P[i] > right,说明回文右边界需要更新。
  5. 返回最长回文子串
    • 由于 P[i] 是在预处理的字符串 t 上计算的,我们需要 转换回原始字符串索引

🔹 示例

输入

String s = "babad";

预处理

# b # a # b # a # d #

计算 P[]

P = [0, 1, 0, 1, 2, 1, 4, 1, 2, 1, 0]

最长回文

可能输出 "bab""aba"


📌 核心循环

这个 for 循环是马拉车算法的核心部分,它用于 构造回文半径数组 P[],同时动态维护当前的 回文中心 center最右扩展边界 right,从而有效地 减少重复计算,保持线性时间复杂度 O(n)O(n)。


💡 变量说明

int center = 0, right = 0; // 当前回文中心 & 最右扩展边界
int maxLen = 0, start = 0;  // 记录最长回文的起始索引
int[] P = new int[n];       // 记录以 i 为中心的最大回文扩展半径
  • P[i]:存储i 为中心的最大回文半径(不包括自身)。
  • center:当前正在扩展的回文中心索引。
  • right:当前回文中心的最右扩展边界(不包含)。
  • maxLen & start:用于存储最长回文子串的信息。

📖 代码拆解

for (int i = 0; i < n; i++) {

遍历预处理后的字符串 tnt.length(),即 #b#a#b#a#d# 这样的格式)。


🟢 1️⃣ 计算 P[i] 的初始值

int mirror = 2 * center - i; // i 关于 center 的对称点
if (i < right) {P[i] = Math.min(right - i, P[mirror]); // 利用对称性减少扩展
}

🔹 解释:

  • mirror = 2 * center - i 计算 i 关于 center对称位置
  • 如果 iright 之内(说明i还在当前回文范围内),我们可以利用 mirror 位置的 p[mirror] 直接赋值P[i],减少计算:
    • 如果 P[mirror] < right - i,则 P[i] = P[mirror](对称性)。
    • 否则,P[i] = right - i(避免越界)。
  • 目的:减少扩展的计算次数,使算法保持 O(n) 复杂度。

🟠 2️⃣ 继续扩展 P[i]

while (i + P[i] + 1 < n && i - P[i] - 1 >= 0 &&t.charAt(i + P[i] + 1) == t.charAt(i - P[i] - 1)) {P[i]++;
}

🔹 解释:

  • P[i] 的基础上,尝试继续向左右扩展回文子串
  • 只要:
    1. i + P[i] + 1 不超出字符串范围
    2. i - P[i] - 1 不超出左边界
    3. t.charAt(i + P[i] + 1) == t.charAt(i - P[i] - 1)(回文对称)。
  • 那么 P[i]++,继续扩展

🟡 3️⃣ 更新 centerright

if (i + P[i] > right) {center = i;right = i + P[i];
}

🔹 解释:

  • 如果 i + P[i](回文右边界)超过 right(当前已知的最右边界),说明新的回文子串更靠右,所以需要更新 centerright
  • 这样可以使未来的 P[i] 计算尽可能复用对称回文信息

🔴 4️⃣ 记录最长回文子串

if (P[i] > maxLen) {maxLen = P[i];start = (i - maxLen) / 2; // 计算原字符串中的索引
}

🔹 解释:

  • 如果 P[i] 大于 maxLen,说明找到了更长的回文子串,更新 maxLenstart
  • start = (i - maxLen) / 2:因为 i预处理后的字符串中的索引,所以需要转换回原始字符串的索引。

📌 例子演示

假设 s = "babad",预处理后:

t = # b # a # b # a # d #

初始化:

center = 0, right = 0, P = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

📍 迭代 i = 1

  • mirror = 2 * 0 - 1 = -1(无效)。
  • right = 0,所以 P[i] = 0
  • t[0] == t[2](都是 #),扩展 P[i]++
P[1] = 1
  • 更新 center = 1, right = 2

📍 迭代 i = 2

  • mirror = 0P[mirror] = 0P[2] = 0
  • t[1] == t[3](都是 b),扩展:
P[2] = 1
  • t[0] == t[4](都是 #),继续扩展:
P[2] = 2
  • 更新 center = 2, right = 4

📍 继续迭代

不断更新 P[i],最终找到最长回文 "bab""aba"


📝 复杂度分析

  • 每个字符最多扩展一次,总时间复杂度是 O(n)

💡 总结

  • 利用 mirror 对称性,减少重复计算。
  • 维护 centerright,尽可能跳过已计算的部分。
  • 保持 O(n) 复杂度,适用于超长字符串回文匹配问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/893386.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

47.9K star!全平台开源笔记神器,隐私安全首选!

"Joplin 是一款开源的笔记记录和待办事项应用,支持端到端加密同步,完美替代商业笔记软件" —— 来自开发者 Laurent Cozic嗨,大家好,我是小华同学,关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法"Joplin 是一款开源的笔记记录和待办事项应…

什么是项目管理?五大流程是什么?

项目管理这东西,大家可能听过,但是具体怎么操作,很多人可能就一头雾水了。 简单来说,项目管理就是为了完成某个特定目标,把时间、资源、人员等因素有条不紊地组织、协调、控制和调整,最终确保项目按时、按预算、高质量地完成。 这个“项目”可以是任何东西,比如公司新产…

最全面的浏览器教程-完结撒花

完结撒花 🎉🎉🎉完结撒花 🎉🎉🎉 《最全面的浏览器教程》目前要告一段落了,耗时我一个多月,整理了大量的笔记,参考了很多教程和文章,累计写了 24 篇文章,7.3 万字,200 多张图片,收获了很多粉丝和鼓励,在此表示感谢。 如果后续有学到什么新技巧,看到好玩意…

牛客题解 | 字符串相乘

牛客题库题解题目 题目链接 题解 题目难度:中等难度 知识点:字符串、大数相乘 模拟人工计算 计算的过程基本上和小学生列竖式做乘法相同。为编程方便,并不急于处理进位,而将进位问题留待最后统一处理。数组a存储第一个数字123(逆序)数组b存储第二数组20(逆序)结果存入数…

皮尔逊、斯皮尔曼、肯德尔相关系数

相关系数和特征选择 相关系数和特征选择,一个是属性,一个是特征。一般,把数据集中的各列成为属性,而对算法模型表现有益的属性成为特征。例如,在预测泰坦尼克乘客的存活情况时,乘客姓名这个属性对我们的预测可能没有帮助,甚至会干扰模型表现;而乘客年龄、性别或许与存活…

认识 TapFlow,以编程方式运行 TapData

TapFlow 是 TapData Live Data Platform 最新推出的一个面向编程的API 框架。TapFlow 可以让开发者和数据工程师用一个简单易用而又强大的编程语言来进行数据管道和数据模型的开发工作。**什么是TapFlow? ** TapFlow 是 TapData Live Data Platform 最新推出的一个面向编程的A…

Maya 影视渲染,渲染101 让创作无压力!

Maya 创作时,渲染是不是常让人崩溃?漫长等待、电脑性能不足、报错频出,今天就给大家分享基于渲染 101 平台的 Maya 云渲染,轻松解决这些难题!告别漫长等待,效率飙升**** 自己电脑渲染复杂 Maya 项目,耗时久,进度慢。渲染 101 的云渲染有强大计算集群,众多高性能服务器…

Java SpringBoot 升级后,编译打包都没问题,运行报错

编译打包都没问题,运行报错 10:36:39,587 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@5966cc - Registering current configuration as safe fallback point Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/core…

在Hyper-V虚拟化平台上,怎么创建和管理虚拟机呢?

确实,在Hyper-V虚拟化平台上,创建和管理虚拟机(VMs)是实现资源高效利用和业务灵活部署的关键。以下是对这一观点的详细阐述:一、创建虚拟机:资源高效利用的基础 资源分配与优化: 在创建虚拟机时,管理员需要根据业务需求合理分配CPU、内存、存储和网络等资源。通过精确的…

STM32实战——ESP8266 WIFI模块

此篇博文提供了ESP8266的开发指南,包括在STM32上使用ESP8266进行WiFi连接、发送和接收HTTP请求以及在ESP8266中使用AT指令发送GET方式请求等内容。ESP8266 硬件介绍 ESP8266系列模组有哪些:在本实验中,ESP8266与ESP-01不做区分。 ESP-01引脚介绍:引脚 功能3.3 3.3V供电,避…

一招学会Prometheus对接三方监控平台

文章来源:乐维社区 通过将Prometheus与不同的监控工具和服务集成,企业可以实现对更广泛资源和服务的监控,包括那些不由Prometheus原生支持的系统。这种集成不仅有助于获取更全面、深入的监控数据,还能提升故障排查和性能优化的效率,从而确保系统的稳定性和可靠性。 环境说…