中心扩展算法
主要用于解决寻找最长回文串问题。
核心思想:从回文中心(一个字符或两个相等的字符)向左右边界扩展。
状态转移链:P(i,j) <—— P(i+1, j-1) <—— P(i+2,j-2)<——......<——某一回文中心
public class Solution{
public String longestPalindrome(String s){
int len = s.length();
if(len < 2)
return s;
int low=0, high=0;
//扩展
for(int i=0; i<len; ++i){
int lenFor1 = expandAroundCenter(s, i, i);
int lenFor2 = expandAroundCenter(s, i, i+1);
int lenForSub = Math.max(lenFor1, lenFor2);
if(lenForSub > high-low+1){
low = i - (len-1)/2;
high = i + len/2;
}
}
return s.substring(low, high+1);
}
public int expandAroundCenter(String s, int left, int right){
while(left>=0 && right<s.length() && s.charAt(left)==s.charAt(right)){
--left;
++right;
}
return right-left-1;
}
}
Manacher算法
核心思想:最长回文半径。采用回文半径数组pArr来记录遍历过程中以该点为中心的回文半径。
采用Manacher算法解决寻找最长回文串问题。
步骤
字符串预处理——>更新回文半径数组pArr——>根据数组中的回文半径最大值计算
字符串预处理
通过给原字符串增加特殊字符的方式统一奇回文串和偶回文串的情况,使我们可以按照回文中心为一个字符的情况向字符两侧扩展,寻找最长子回文串。
原字符串s='bcba'——>处理后的字符串s'="#b#c#b#a#"
注:增加的特殊字符没有要求,可以是原字符中出现过的字符。
更新回文半径数组pArr(情况证明在补充)
更新信息:
回文半径数组pArr, 回文右边界R(初始-1),回文中心坐标C(更新回文右边界R时的回文中心)
更新情况1:R-1 < i——> R <= i
此时没有任何可利用的回文信息。采用中心扩展算法计算以i为回文中心的最长回文半径。
更新情况2:R-1 >= i——>R > i
此时用的可以来更新pArr[i]的信息是:
以i位置之前的所有位置为回文中心的回文半径信息pArr[j], j<i
回文右边界R:之前遍历的所有位置的回文串所能达到的最右边的下一个位置
回文中心C:上一次更新R时的回文中心坐标
此时:i ∈ (C,R)
再设定三个变量参数
i'(i关于C的对称位置),r和l(i'最长回文字符串的左右边界),L(R-1关于C的对称位置)
更新情况2-1
i' 的最长回文字符串的左右边界 l, r ∈ (L, R-1)
此时,i 的最长回文字符串长度与 i' 一样
更新情况2-2
i' 的最长回文字符串的左边界超出了L,l < L
那么此时回文中心 i 的最长回文字符串是(L,L')等同于 (R, R')
更新情况2-3
i' 的最长回文字符串的左边界与L重合
那么以 i 位置为中心的最长回文字符串至少是以 i'为位置中心的最长回文字符串
并且还要继续扩展。
三种情况的图示如下:
代码
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
// 修改字符串
StringBuilder newStr = new StringBuilder();
for (int i = 0; i < len; i++) {
newStr.append("#").append(s.charAt(i));
}
newStr.append("#");
int newLen = newStr.length();
// 回文半径数组、回文中心C、回文右边界R
int[] pArr = new int[newLen];
int R = -1;
int C = -1;
for (int i = 0; i < newLen; ++i) {
if (i < R) {
int mirr = 2 * C - i; // i 关于 C 的对称位置
int L = 2 * C - R + 1; // L 为 R-1 关于 C 的对称位置
// 情况2-1: i' 的最长回文子串完全在 L 和 R-1 之内
if (pArr[mirr] < R - i) {
pArr[i] = pArr[mirr];
}
// 情况2-2: i' 的最长回文子串左边界超出了 L
else if (pArr[mirr] > R - i) {
pArr[i] = R - i;
}
// 情况2-3: i' 的最长回文子串左边界与 L 重合
else {
pArr[i] = R - i;
while (i + pArr[i] + 1 < newLen && i - pArr[i] - 1 >= 0
&& newStr.charAt(i + pArr[i] + 1) == newStr.charAt(i - pArr[i] - 1)) {
pArr[i]++;
}
}
} else {
// 情况1: i 在右边界 R 之外,直接进行中心扩展
pArr[i] = 0;
while (i + pArr[i] + 1 < newLen && i - pArr[i] - 1 >= 0
&& newStr.charAt(i + pArr[i] + 1) == newStr.charAt(i - pArr[i] - 1)) {
pArr[i]++;
}
}
// 更新 R, C
if (i + pArr[i] > R) {
R = i + pArr[i];
C = i;
}
}
// 找到最大回文子串
int maxLen = 0;
int centerIndex = 0;
for (int i = 0; i < newLen; ++i) {
if (pArr[i] > maxLen) {
maxLen = pArr[i];
centerIndex = i;
}
}
int start = (centerIndex - maxLen) / 2; // 将扩展后的索引转换回原始字符串的索引
return s.substring(start, start + maxLen);
}
public int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return right - left - 1;
}
}
优化代码,对上面的情况进行整合。
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2)
return s;
// 修改字符串
StringBuilder newStr = new StringBuilder();
newStr.append("#");
for (int i = 0; i < len; i++) {
newStr.append(s.charAt(i));
newStr.append("#");
}
int newLen = newStr.length();
int[] pArr = new int[newLen];
int R = 0;
int C = 0;
for (int i = 0; i < newLen; ++i) {
int mirr = 2 * C - i;
if (i < R)
pArr[i] = Math.min(R - i, pArr[mirr]);
else
pArr[i] = 0;
// 尝试扩展回文半径
while (i + pArr[i] + 1 < newLen && i - pArr[i] - 1 >= 0
&& newStr.charAt(i + pArr[i] + 1) == newStr.charAt(i - pArr[i] - 1)) {
pArr[i]++;
}
// 更新R和C
if (i + pArr[i] > R) {
R = i + pArr[i];
C = i;
}
}
// 找到最大回文子串
int maxLen = 0;
int centerIndex = 0;
for (int i = 0; i < newLen; ++i) {
if (pArr[i] > maxLen) {
maxLen = pArr[i];
centerIndex = i;
}
}
int start = (centerIndex - maxLen) / 2;
return s.substring(start, start + maxLen);
}
}
时间复杂度O(n)。
补充
情况2-1: i' 的最长回文字符串的左右边界 l, r ∈ (L, R-1)
设 i' 的左右边界外的第一个字符分别为X, Y;X,Y关于C对称的位置分别为N, M
那么,C是回文字符串的回文中心,i i'关于C对称,
1. 由设定条件可知:X != Y,M,N关于 i' 对称
2. 由于 i' 和 i 关于C对称,X和N,Y和M关于C对称,则可知:
X == N, Y == M
那么可知 M != N
3.而在范围(M, N)内,由与 i' 的对称关系可知,以 i 为对称点的位置对的字符相等
则得到了以 i 为回文中心的最长回文字符串,其左右边界外的第一个字符分别为M, N。
其回文长度与 i' 相同。
情况2-2 i' 的最长回文字符串的左边界超出了L,l < L
依据情况1的设定和推导,因为不知X和N的关系,所以不能推出情况1的结果
设 L关于 i' 的对称位置是 L',R-1关于 i 的对称位置是R-1'。
设 L 左侧往外的第一个位置为X,L' 右侧往外的第一个位置是Y。
设 R-1' 左侧往外的第一个位置为M,R-1 右侧往外的第一个位置是N。
那么,C是回文字符串的回文中心,i i'关于C对称,
1. 由设定可知,X == Y, X != N
2. 由于 i' 和 i 关于C对称,X和N,Y和M关于C对称,则可知:
Y == M
那么可以推出 M != N
3. .而在范围(M, N)内,由与 i' 的对称关系可知,以 i 为对称点的位置对的字符相等
则得到了以 i 为回文中心的最长回文字符串,其左右边界外的第一个字符分别为M, N。
其回文长度范围为[R-1', R-1]
情况2-3 i' 的最长回文字符串的左边界与L重合
根据情况2-2的推导,我们不再能得出X == Y,而是X != Y, 此时无法判断M, N的关系。
但我们可以确定的是[R-1', R-1]的范围一定是满足回文串的,因此我们需要探索的是往外是否还满足回文串,采用中心扩展。