题意:找给定字符串最长的回文子串
解题:马拉车算法提供了解决该题的线性方法。主要思想:从左到右依次遍历每个字符以及每个空位(因为回文字符串即有双数又有单数),对于每个位置,拓展寻找最长字串。每找一次回文字符串,如果大于设定的右边界,就拓展右边界,如果在右边界所属字串中心位置和右边界范围内,它必定与p[2*id - i]相同(即一个回文字符串右部分与左部分是对称的,我前面已经解决了左部分,现在解决右部分就可以直接在左部分基础上进行,但还有一种例外,就是左部分也是回文字符串,且它的半径延申到所在字符串的左边界以外,那么我的右部分就不能直接等于左串的值,因为还有左右边界的限定)。
解决如何遍历空隙的问题:我们在字符串每个字符两边都插入一个相同的字符,代替空格。比如123,就变成#1#2#3#
解决字符串增长后如何返回子串的问题:我们要在原来未更改的字符串中返回字串,假设原来字符串是s,改变后的字符串是sb,显然我找到sb中最长字串的半径,减一后就是s中最长字串的长度,比如#2#2#1#2#2#半径为6,22122长度为5。长度找到了,还要找到起始位置,比如在#1#2#2#1#2#2#回文子串中心的位置为7,子串长度6,7-6=1,122122中起始位置为1,正确,#b#o#b#中心位置3,半径为4,3-4=-1,错误。。。那么这个问题怎么解决?我们可以在最前面再添加一个字符$(验证略),这样就没问题了。
核心代码:p[i] = mx > i ? Math.min(p[2*id-i], mx-i) : 1;
public class Main{
public static String findstr(String s) {
StringBuilder sb = new StringBuilder();
sb.append("$#");
for (int i = 0; i < s.length(); i++) {
sb.append(s.charAt(i));
sb.append("#");
}
int[] p = new int[sb.length()];
//mx右边界,id右边界主人中心,resLen最长子串长度,resCenter最长子串中心
int mx = 0, id = 0, resLen = 0, resCenter = 0;
for (int i = 1; i < sb.length(); i++) {
p[i] = mx > i ? Math.min(p[2*id-i], mx-i) : 1;
while (i+p[i]<sb.length() && sb.charAt(i + p[i]) == sb.charAt(i - p[i])) ++p[i];
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
resCenter = i;
}
}
return s.substring((resCenter-resLen)/2, (resCenter-resLen)/2+resLen-1);
}
public static void main(String[] args){
String s1 = "123321123456543";
System.out.println(findstr(s1));
}
}