文章目录
一、算法概述
- 定义:Manacher算法用于在给定字符串中高效地找出最长回文子串,通过对字符串进行预处理和利用回文子串的对称性,将时间复杂度优化至线性。
- 核心思想:
- 对原字符串插入特殊字符,将奇数长度和偶数长度的回文统一为奇数长度回文。
- 利用已计算的回文半径,通过对称性减少重复计算,快速扩展新位置的回文半径。
- 应用场景:
- 字符串处理中查找最长回文子串(如文本编辑器的回文检测)。
- 密码学中的模式匹配(检测回文结构的密码特征)。
二、算法思路
- 字符串预处理:
- 在原字符串的每个字符之间插入一个特殊字符(如
$),并在首尾添加不同的特殊字符(如#),使所有回文子串长度变为奇数。例如,字符串"aba"变为"#a b b ba#"。
- 在原字符串的每个字符之间插入一个特殊字符(如
- 回文半径数组:
- 定义数组
p[i]表示以字符str[i]为中心的最长回文子串的半径(包含自身)。例如,"#a b b ba#“中p[3] = 4(以b为中心的回文子串为"a b b ba”,半径为4)。
- 定义数组
- 利用对称性加速:
- 若已知
str[ct]为中心的回文子串覆盖str[i](即i < r,其中r为当前最右回文边界),则str[i]的回文半径p[i]可利用str[2*ct-i]的对称性快速初始化。
- 若已知
- 回文半径扩展:
- 从初始化的
p[i]开始,向两侧扩展,直到字符不匹配或超出边界,更新p[i]的最大值。
- 从初始化的
- 边界更新:
- 每次扩展后,若
str[i]的回文边界超过当前最右边界r,则更新中心位置ct和边界r。
- 每次扩展后,若
三、伪代码实现
1. 数据结构与全局变量
常量 maxn = 1000010 # 最大字符串长度
常量 split = '$' # 插入的特殊字符
数组 p[maxn] # 记录每个字符的回文半径
数组 strTmp[maxn] # 临时存储原字符串
2. 核心函数框架
算法:Manacher算法
输入:字符串 str
输出:最长回文子串的长度
函数 ManacherPre(str):
# 字符串预处理:插入特殊字符
将 str 复制到 strTmp
i = 0
while strTmp[i] 不为空字符:
str[2*i] = split
str[2*i+1] = strTmp[i]
i = i + 1
str[2*i] = split
str[2*i+1] = 空字符
函数 ManacherMatch(a, b):
# 判断两个字符是否相等
return a == b
函数 Manacher(str):
ManacherPre(str) # 预处理字符串
ct = 0 # 当前最右回文区域的中心位置
r = 0 # 当前最右回文区域的右边界
maxLen = 1 # 最长回文子串的长度
p[0] = 1 # 初始化第一个字符的回文半径
for i from 1 to str的长度 - 1:
# 1. 计算 p[i] 的初始值
if i < r:
p[i] = min(p[2*ct-i], r-i)
else:
p[i] = 0
# 2. 扩张 p[i],让 p[i] 达到最大值
while i - p[i] >= 0 且 ManacherMatch(str[i-p[i]], str[i+p[i]]):
p[i] = p[i] + 1
# 3. 更新 ct 和 r
if i + p[i] > r:
ct = i
r = i + p[i]
# 4. 更新最长回文
if 2*p[i] - 1 > maxLen:
maxLen = 2*p[i] - 1
return maxLen
# 主程序(示例)
输入字符串 str
输出 Manacher(str)
四、算法解释
1. 预处理过程
- 通过插入特殊字符,将所有回文子串统一为奇数长度,简化后续计算。例如,“abba"变为”#a b b bb$a#",每个回文子串中心明确。
2. 回文半径初始化
- 对称性利用:若
i在当前最右回文区域内(i < r),则p[i]可初始化为p[2*ct-i]和r-i的较小值。- 原理:由于回文的对称性,
str[i]与str[2*ct-i]的回文半径在一定范围内相同,r-i确保不超出当前已知回文边界。
- 原理:由于回文的对称性,
- 边界外情况:若
i >= r,则p[i]初始化为0,需从头开始扩展。
3. 回文半径扩展
- 从初始化的
p[i]开始,通过比较str[i-p[i]]和str[i+p[i]],不断扩大回文半径,直到字符不匹配或超出边界。
4. 边界更新与结果记录
- 每次扩展后,若
i + p[i]超过当前最右边界r,则更新ct = i和r = i + p[i],为后续计算提供新的对称参考。 - 记录
2*p[i] - 1的最大值(减去1是因为半径包含中心字符),即为最长回文子串的长度。
5. 示例演示
- 原字符串:“abba”
- 预处理后:“#a b b bb$a#”
- 计算过程:
i=0:p[0]=1,更新ct=0, r=1。i=1:p[1]=2(对称于p[0]),更新ct=1, r=3。i=2:p[2]=1,不更新边界。i=3:p[3]=4(扩展得到最长回文"a b b ba"),更新ct=3, r=7。- 最终最长回文长度为
2*4 - 1 = 7(对应原字符串"abba")。
五、复杂度分析
- 时间复杂度:
- 预处理:O(n),插入特殊字符遍历一次字符串。
- 回文半径计算:每个字符最多被访问一次(扩展过程中每个字符仅需比较一次),总时间复杂度为O(n)。
- 整体时间复杂度:O(n)。
- 空间复杂度:
- 存储回文半径数组
p和临时字符串strTmp,空间复杂度为O(n)。
- 存储回文半径数组
- 优势与局限:
- 优势:线性时间解决最长回文子串问题,比暴力枚举(O(n²))高效。
- 局限:需额外空间存储预处理字符串和回文半径数组,不适用于空间受限场景。
本文为作者(英雄哪里出来)在抖音的独家课程《英雄C++入门到精通》、《英雄C语言入门到精通》、《英雄Python入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。
270

被折叠的 条评论
为什么被折叠?



