1. Manacher算法的基本概念
什么是Manacher算法?
Manacher算法是一种用于在 线性时间复杂度(O(n)O(n))内求解字符串中最长回文子串的算法。它的核心思想是通过 对称性 和 动态规划 来避免重复计算。
为什么需要Manacher算法?(优化时间)
- 暴力解法:枚举所有子串并检查是否为回文,时间复杂度为 O(n2)O(n2)。
- Manacher算法:通过利用回文的对称性,将时间复杂度优化到 O(n)O(n)。
2. Manacher算法的核心思路(图)
1. p[i]表示以第i个字符的最大回文半径。
2. 当遍历到当前位置,因为i = 3的回文半径将当前位置包裹起来的,所以i = 5的回文半径至少是1,这个可有i = 1直接得到,i = 1与i = 5是关于i = 3镜像对称,此后对i = 5的位置继续向外暴力扩展,求得最终p[i]。
3.对每个位置遍历完都得比较记录最大的半径的位置,和最右边被包裹的最大位置,以方便后续持续使用镜像直接得值。
如上遍历情况,所以最终可得划线的答案依次为:0,3,0,1,0。
3. Manacher算法核心思路(代码处理)
预处理(统一处理为奇数个字符串)
为了统一处理奇数和偶数长度的回文子串,Manacher算法首先对字符串进行预处理:
- 在字符串的每个字符之间插入一个特殊字符(如
#
),并在字符串的开头和结尾也插入特殊字符。 - 例如,字符串
abba
预处理后变为#a#b#b#a#
。
定义数组
- 设
p[i]
表示以字符s[i]
为中心的最长回文半径(包括中心字符)。 - 例如,对于字符串
#a#b#b#a#
,p[3] = 4
(回文子串为#a#b#b#a#
)。
利用对称性
- 设
C
为当前已知的最长回文子串的中心,R
为其右边界。 - 对于当前字符
s[i]
,如果i < R
,则可以利用对称性快速计算p[i]
。
状态转移
- 如果
i < R
,则p[i] = min(R - i, p[2 * C - i])
。 - 否则,从
i
开始向左右扩展,计算p[i]
。
4. Manacher算法的代码实现
以下是 Manacher算法 的实现(Java):
public class Manacher {
// Manacher算法
public static String longestPalindrome(String s) {
if (s == null || s.length() == 0) return "";
// 预处理字符串
StringBuilder sb = new StringBuilder();
sb.append('^'); // 开头特殊字符
for (char c : s.toCharArray()) {
sb.append('#').append(c);
}
sb.append("#$"); // 结尾特殊字符
String t = sb.toString();
int n = t.length();
int[] p = new int[n]; // p[i] 表示以 t[i] 为中心的最长回文半径
int C = 0, R = 0; // C 是中心,R 是右边界
for (int i = 1; i < n - 1; i++) {
// 利用对称性快速计算 p[i]
int mirror = 2 * C - i;
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]++;
}
// 更新中心和右边界
if (i + p[i] > R) {
C = i;
R = i + p[i];
}
}
// 找到最长回文子串
int maxLen = 0, center = 0;
for (int i = 1; i < n - 1; 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) {
String s = "babad";
String result = longestPalindrome(s);
System.out.println("最长回文子串: " + result); // 输出:bab 或 aba
}
}