查找一个字符串的最长回文子串的线性算法。时间复杂度为O(n)
1. 原理
在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符不能在原串中出现,一般情况下可以用#号,使字符串长度变成奇数个。
(1)Len数组性质
用一个辅助数组Len[i]表示以字符T[i]为中心的最长回文字串半径长度。
注:Len[i]-1就是该回文子串在原字符串S中的长度,证明:以T[i]为中心的最长回文字串长度是2*Len[i]-1 ,其中分隔符的数量一定比其他字符的数量多1,即有Len[i]-1个字符来自原字符串。
(2)最右回文右边界R
这个位置及之前的位置的回文子串,所到达的最右边的地方。
(3)、最右回文右边界的对称中心C
最右回文右边界的中心点C,如下图,p=4时,R=6,C=3,最早的对应R的中心点
2. 算法流程
- 第一种情况:下一个要移动的位置p1在最右回文右边界R的右边。
将移动的位置为对称中心,向两边扩,同时更新回文半径数组,R和对称中心C。 - 第二种情况:下一个要移动的位置p1就是R或是在R的左边
在这种情况下又分为三种:
p2是p1以C为对称中心的对称点;
pL是以p2为对称中心的回文子串的左边界;
cL是以C为对称中心的回文子串的左边界。- cL < pL:p1的回文半径就是p2的回文半径radius[p2]。
- cL > pL:p1的回文半径就是p1到R的距离R-p1+1。
- cL = pL:p1的回文半径继续往外扩,但只需从R之后往外扩就可,扩了之后更新R和C。
- cL < pL:p1的回文半径就是p2的回文半径radius[p2]。
3. 代码实现
public int manacher(String s) {
if(s == null || s.length() == 0) return 0;
char[] charArr = manacherString(s);
int[] radius = new int[charArr.length];
int R = -1;
int c = -1;
int max = Integer.MIN_VALUE;
for (int i = 0; i < radius.length; i++) {
radius[i] = R > i ? Math.min(radius[2 * c - i], R - i + 1) : 1;
while(i+radius[i] < charArr.length && i - radius[i] > -1){
if(charArr[i - radius[i]] == charArr[i + radius[i]])
radius[i]++;
else
break;
}
if(i + radius[i] > R){
R = i + radius[i] - 1;
c = i;
}
max = Math.max(max, radius[i]);
}
return max - 1;
}
public static char[] manacherString(String str){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
sb.append("#");
sb.append(str.charAt(i));
}
sb.append("#");
return sb.toString().toCharArray();
}
4. 例题
描述:一个字符串只能向后面添加字符,让整个字符串变成回文串,求添加最短字符
解析:求包含最后一个字符的最长回文串,前面不是的部分,逆序过来,添加到字符串尾部即可,即当R到达最后一个字符,由C可得到最左边界,最左边界之前的部分逆序。
public int longestPalindrome(String s) {
if(s == null || s.length() == 0) return 0;
char[] charArr = manacherString(s);
int[] radius = new int[charArr.length];
int R = -1;
int c = -1;
int maxContainsEnd = -1;
for (int i = 0; i < radius.length; i++) {
...
if(R == redius.length - 1){
maxContainsEnd = redius[i];
break;
}
}
char[] res = new char[s.length() - maxContainsEnd + 1];
for(int i = 0; i < res.length; i++){
res[res.length - 1 - i] = redius[i * 2 + 1];
}
return String.valueOf(res);
}