问题
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
扩展中心法
1、最长子串的长度可能是奇数,也可能是偶数个。所以我们需要选取一个字符或两个字符作为中心进行扩散
2、找到关于中心对称的最长子字符串即为所求
马拉车算法
该算法改进了扩展中心算法(以下内容可能与原始算法有出入)。
1、为了避免处理奇数长度和偶数长度,先对原字符串进行处理。在原字符串的基础上间隔插入符号’#’(该符号即使会出现在原字符串也不受影响)。形如:
abc → #a#b#c#
ab → #a#b#c
这么做可以保证我们选取一个字符作为中心时,考虑到了所有可能的中心点,原字符不用考虑,新增的’#'实际上是考虑了以两个元素作为中心。
2、用数组r[i]记录每个元素作为中心时,最长对称子串的半径(不包括自身,即字符串a的半径为0)
该算法核心的地方就是借用了之前的r[i]内容已达到减少重复计算的目的。
position——当前对称子串长度最长的中心位置
rm——最长对称子串的半径
right——以position为中心,能达到的最右侧位置
if(i<right) {
int j = 2*position-i; //对称位置
if(right<i+r[j]) { //超出界线
r[i] = right-i;
} else {
r[i]=r[j]; //不一定是最终结果
}
}
以上代码解释了应该如何借助已有半径来计算r[i]。
- 如果i>=right,那么就无法借用已知信息,只能以此为中心向两侧扩展。
- 如果i<right,那么可以借用r[j]来初始化r[i],已达到减少重复计算的目的(j是i关于position对称的位置)。此时也会有两种情况:
(1)以j为中心的最长对称子串全部在以position为中心的最长子串范围,则r[i]=r[j],考虑到可能到达边界,因此还需要进一步确认是否以为最长;
(2)以j为中心的最长对称子串有部分不在以position为中心的最长子串范围,则超出部分不保证关于position对称,则r[i]=right-i,然后再去按照扩展中心法进一步调整r[i]
3、截取最长回文子串,完整的代码如下:
class Solution {
public String longestPalindrome(String s) {
if(s.equals("")) {
return "";
}
//预处理
StringBuilder sb = new StringBuilder();
for(int i=0 ; i<s.length() ; i++) {
sb.append('#');
sb.append(s.charAt(i));
}
sb.append('#');
int[] r = new int[sb.length()];
int position = 0;//记录拥有最长回文串的中心
int rm = 0;//记录最长回文子串的半径
int right = position+rm ;//记录最长回文子串能到的最右边的位置
for(int i=0 ; i<sb.length() ; i++) {
if(i<right) {
int j = 2*position-i; //对称位置
if(right<i+r[j]) { //超出界线
r[i] = right-i;
} else {
r[i]=r[j]; //不一定是最终结果
}
}
int start = i+r[i]+1;
while(true) {
if(2*i-start<0||start>=sb.length()) {
break;
} else {
if(sb.charAt(2*i-start)!=sb.charAt(start)) {
break;
} else {
r[i]++;
start++;
}
}
}
if(r[i]>rm) {
rm = r[i];
position = i;
right = position+rm;
}
}
String tmp = sb.substring(2*position-right,right+1);
StringBuilder res = new StringBuilder();
for(int i=0 ; i<tmp.length() ; i++) {
if(tmp.charAt(i)!='#') {
res.append(tmp.charAt(i));
}
}
return res.toString();
}
}