利用辅助数组P(数组长度与字符串长度相同)记录以每个字符为中心的最长回文半径
已知字符下标(i)和以该字符为中心的最长回文半径,则可获取最长回文子串。
但该算法存在一个问题:只能求长度为奇数的回文子串。
解决方法:每个相邻字符用特殊字符分割(如:#),包括首尾
例:
原字符串: a b a a b
新字符串:# a # b # a # a # b #
P[]: 1 2 1 4 1 2 5 2 1 2 1
核心问题:需计算辅助数组P[] (由于用特殊字符分隔相邻字符,则数组P长度为2*str.length() + 1).
计算辅助数组P:
源码:
public class Main {
public static void main(String[] args) {
System.out.println(longestPalindrome("babad"));
}
/**
* Mancher 算法:
* 辅助数组P,记录以每个字符为中心的最长回文半径
* 则只能求回文子串长度为奇数的回文子串,如aba
* 解决方法:每个相邻字符用特殊字符分隔(如:#),包括字符串首尾
* 原字符串: a b a a b
* 新字符串:#a#b#a#a#b#
* P[]:12141252121
* 核心问题:求辅助数组P
* */
public static String longestPalindrome(String s) {
int len = s.length();
int newLen = 2*len +1;
//处理字符串
char[] newStr = new char[newLen];
for(int index = 0; index < len; index++){
newStr[2*index] = '#';
newStr[2*index + 1] = s.charAt(index);
}
newStr[2*len] = '#';
//求辅助数组P
int[] p = new int[newLen];
for(int index = 0; (index < 2*len + 1); index++){
int right = index + 1;
while((2*index-right) >= 0 && right < newLen && newStr[2*index-right] == newStr[right])
right++;
p[index] = right - index;
}
//获取最长回文子串半径
int i = 0;//最长回文子串中心字符下标
for(int index = 0; index < newLen; index++){
i = p[index] > p[i] ? index : i;
}
//获取最长回文子串
String palindrome = "";
for(int index = i+1-p[i]; index < i + p[i]; index++){
palindrome += '#' == newStr[index] ? "" : newStr[index];
}
return palindrome;
}
}
Mancher算法,求辅助数组P[]时,避免了重复计算,即根据P[0]-P[index-1],推断出以index位置的字符为中心的最长回文子串从何处开始判断,而并非每次都从index位置相邻的两个字符开始判断
Mancher算法计算辅助数组P[]:
需要两个变量
maxi:记录0-(index-1)个字符中,最长回文子串最右边界的字符下标
r:即P[maxi],0-(index-1)个字符中,最长回文子串最右边界的字符的最长回文子串半径
max:maxi + r - 1,0-(index-1)个字符中,最长回文子串的最右边界
代码如下: public class Main {
public static void main(String[] args) {
System.out.println(longestPalindrome("baabd"));
}
/**
* Mancher 算法:
* 辅助数组P,记录以每个字符为中心的最长回文半径
* 则只能求回文子串长度为奇数的回文子串,如aba
* 解决方法:每个相邻字符用特殊字符分隔(如:#),包括字符串首尾
* 原字符串: a b a a b
* 新字符串:#a#b#a#a#b#
* P[]:12141252121
* 核心问题:求辅助数组P[]
* */
public static String longestPalindrome(String s) {
int len = s.length();
int newLen = 2*len +1;
//处理字符串
char[] newStr = new char[newLen];
for(int index = 0; index < len; index++){
newStr[2*index] = '#';
newStr[2*index + 1] = s.charAt(index);
}
newStr[2*len] = '#';
//求辅助数组P
int[] p = new int[newLen];
int maxi = 0; //0-(index-1)个字符中,最长回文子串最右边界的字符下标
int r = p[0] = 1; //0-(index-1)个字符中,最长回文子串最右边界的字符的最长回文子串半径
int max = maxi + r - 1; //0-(index-1)个字符中,最长回文子串的最右边界
for(int index = 1; (index < newLen); index++){
//判断以当前字符为中心,求回文子串的右边起始位置,避免重复计算
int temp = 2*maxi-index < 0 ? 0 :
(max > index ? (p[2*maxi-index] < (max-index) ? p[2*maxi-index] : (max-index))-1 : 0);
int right = index + 1 + temp;
//向两边扩展,求回文子串
while((2*index-right) >= 0 && right < newLen && newStr[2*index-right] == newStr[right])
right++;
p[index] = right - index;
if(max < index + p[index] - 1){
maxi = index;
r = p[index];
max = maxi + r - 1;
}
}
//获取最长回文子串半径
int i = 0;//最长回文子串中心字符下标
for(int index = 0; index < newLen; index++){
i = p[index] > p[i] ? index : i;
}
//获取最长回文子串
String palindrome = "";
for(int index = i+1-p[i]; index < i + p[i]; index++){
palindrome += '#' == newStr[index] ? "" : newStr[index];
}
return palindrome;
}
}