Manacher算法
Manacher算法求最长回文子串,时间和空间复杂度都是O(n),难度比较大又比较难记,这里写一下备忘。
Leetcode 5.最长回文子串
给你一个字符串 s
,找到 s
中最长的回文子串。
示例1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
方法1:DP
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
// 记录下最长回文子串的下标和长度
int begin = 0;
int maxLen = 1;
// 建立dp数组
// dp[i][j]指从i到j的数组是否为回文串(包括i,j)
boolean dp[][] = new boolean[len][len];
// 长度为1的是回文串
for (int i = 0; i < dp.length; i++) {
dp[i][i] = true;
}
// 先枚举子串长度
for (int length = 2; length <= len; length++) {
// 再枚举左边界
for (int i = 0; i < len; i++) {
// 右边界
int j = i + length - 1;
if (j >= len) {
break;
}
if (s.charAt(i) != s.charAt(j)) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i+1][j-1];
}
}
if (dp[i][j] == true && j - i + 1 > maxLen) {
begin = i;
maxLen = j - i + 1;
}
}
}
return s.substring(begin, begin+maxLen);
}
}
方法2:中心搜索
class Solution {
public String longestPalindrome(String s) {
if (s.length() <= 1) {
return s;
}
int left = 0;
int right = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = currMax(s, i, i);
int len2 = currMax(s, i, i+1);
int len = Math.max(len1, len2);
if (len > right - left + 1) {
right = i + len / 2;
left = right - len + 1;
}
}
return s.substring(left, right+1);
}
public int currMax(String s, int i, int j) {
while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)) {
i--;
j++;
}
return j - i - 1;
}
}
方法3:Manacher算法
class Solution {
// Manacher
public String longestPalindrome(String s) {
if (s.length() <= 1) {
return s;
}
// 1、数据预处理:把所有字符串添加#
String str = this.addDividers(s, '#');
int len = str.length();
// 臂长数组
int[] p = new int[len];
int center = 0;
int maxRight = 0;
p[1] = 1;
// 对于str而言的
int begin = 0;
int maxLen = 1;
// 对于4种情况,可以优化为两种
for (int i = 0; i < len; i++) {
// 情况2的(1)(3)优化为一个min
if (i < maxRight) {
int mirror = 2 * center - i;
p[i] = Math.min(p[mirror], maxRight - i);
}
// 情况1和情况2的(2)都需要再扩散
// 这里顺便把情况2的(1)(3)也拿去扩散了,但是不影响,因为扩散结果一定还在原地踏步
p[i] = expand(str, i - p[i], i + p[i]);
// 如果maxRight变化,center也要跟着变化
if (i + p[i] > maxRight) {
maxRight = i + p[i];
center = i;
}
// 记录最大的回文子串的长度和开始的下标
if (2 * p[i] + 1 > maxLen) {
maxLen = 2 * p[i] + 1;
begin = i - p[i];
}
}
return delDividers(str, begin, maxLen, '#');
}
// 1、数据预处理:把所有字符串添加#
public String addDividers(String s, char ch) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
sb.append(ch);
sb.append(s.charAt(i));
}
sb.append(ch);
return sb.toString();
}
// 2、中心扩展算法,返回臂长
public int expand(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return (right - left - 2) / 2;
}
// 3、数据后处理:把所有字符串减去#
public String delDividers(String s, int left, int len, char ch) {
StringBuilder sb = new StringBuilder();
for (int i = left; i < left + len; i++) {
if (s.charAt(i) != '#') {
sb.append(s.charAt(i));
}
}
return sb.toString();
}
}