引言
在处理字符串问题时,回文串的查找是一个常见的需求。Manacher算法是一种高效的线性时间复杂度算法,用于查找字符串中的最长回文子串。本文将详细介绍Manacher算法的工作原理,并提供一个基于Java语言的实现示例。
目录
一、问题背景
回文串是正读反读相同的字符串,如“aba”或“abba”。寻找字符串中的最长回文子串时,常见方法有:
-
暴力法:遍历所有子串,检查是否为回文,时间复杂度O(n³)。
-
动态规划:记录子问题的结果,优化至O(n²)。
-
中心扩展法:以每个字符为中心向两侧扩展,时间复杂度O(n²)。
但以上方法均非最优。1975年,Glenn Manacher提出Manacher算法,将时间复杂度降至O(n)。
二、Manacher算法原理
1. 预处理:统一奇偶长度
将原字符串插入特殊字符(如#
),使所有回文子串变为奇数长度。例如:
原字符串: "abba" → 预处理后: "#a#b#b#a#"
首尾添加不同字符(如$
和^
)避免越界检查。
2. 核心概念
-
回文半径数组
p[]
:p[i]
表示以预处理后字符串中位置i
为中心的最大回文半径(含自身)。 -
当前最远右边界
R
:所有已发现回文子串能达到的最右位置。 -
中心点
C
:对应最远右边界R
的回文中心。
3. 算法步骤
预处理字符串。
初始化
C = 0
,R = 0
,p[]
全为0。遍历字符串,对每个位置
i
:
确定初始半径:若
i < R
,利用对称性取min(p[mirror], R - i)
。中心扩展:尝试扩展回文半径。
更新
C
和R
:若i
的右边界超过R
,则更新C = i
,R = i + p[i]
。找出
p[]
中的最大值,确定最长回文子串。
三、Java代码实现
public class ManacherAlgorithm {
public static String longestPalindrome(String s) {
if (s == null || s.isEmpty()) return "";
// 预处理字符串
StringBuilder processed = new StringBuilder("$#");
for (char c : s.toCharArray()) {
processed.append(c).append("#");
}
processed.append("^");
String T = processed.toString();
int n = T.length();
int[] p = new int[n];
int C = 0, R = 0;
for (int i = 1; i < n - 1; i++) {
int mirror = 2 * C - i; // 计算i关于C的对称点
// 确定初始半径
if (i < R) {
p[i] = Math.min(R - i, p[mirror]);
}
// 中心扩展
while (T.charAt(i + p[i] + 1) == T.charAt(i - p[i] - 1)) {
p[i]++;
}
// 更新C和R
if (i + p[i] > R) {
C = i;
R = i + p[i];
}
}
// 找出最大回文半径及中心
int maxLen = 0;
int center = 0;
for (int i = 0; i < n; i++) {
if (p[i] > maxLen) {
maxLen = p[i];
center = i;
}
}
// 计算原字符串中的起始位置
int start = (center - maxLen) / 2;
return s.substring(start, start + maxLen);
}
public static void main(String[] args) {
System.out.println(longestPalindrome("babad")); // 输出 "bab" 或 "aba"
System.out.println(longestPalindrome("cbbd")); // 输出 "bb"
}
}
四、算法分析
-
时间复杂度:O(n),每个字符最多被访问两次。
-
空间复杂度:O(n),用于存储预处理字符串和回文半径数组。
五、总结
Manacher算法通过对称性利用和预处理技巧,高效解决最长回文子串问题。其核心在于维护回文半径数组和动态更新最远右边界,避免重复计算。掌握这一算法,可显著提升字符串处理效率。该算法不仅限于查找最长回文子串,还可以应用于其他涉及回文串的问题中。希望这篇文章能帮助你理解和掌握Manacher算法的基本原理及其应用。