题目大意
求一个字符串的最长回文子序列。
Manacher算法原理
首先,Manacher算法提供了一种巧妙地办法,将长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现,一般情况下可以用#号。下面举一个例子
原串: aaaabaa
转换后: @#a#a#a#a#b#a#a#a#$
Len数组有一个性质,那就是Len[i]-1就是该回文子串在原字符串S中的长度。首先在转换得到的字符串T中,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*Len[i]-1,经过观察可知,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有Len[i]个分隔符,剩下Len[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为Len[i]-1。有了这个性质,那么原问题就转化为求所有的Len[i]。
参考文献
http://larrylisblog.net/WebContents/images/LongestPalindrom.pdf
class Solution {
public String longestPalindrome(String s) {
//Manacher算法求最长回文子串
//为了便于奇偶回文串同时处理
//预处理,将字符串中间添加特殊字符,e.g. aaaa -> @#a#a#a#a#$
StringBuilder newstring = new StringBuilder();
newstring.append("@");
for(int i = 0;i < s.length();i++){
newstring.append("#");
newstring.append(s.charAt(i));
}
newstring.append("#$");
//System.out.println(newstring);
//求回文半径
int mx = 0;//记录当前求得的回文序列右端点的最大值
int po = 0;//相应的该回文序列的中心
int maxlen = 0;//最长回文半径
int maxpo = 0;//最长回文中心
//p[]存放的是回文子串的半径
int[] p = new int[newstring.length()];
for(int i = 1;i < newstring.length()-1;i++){
//类似于KMP算法,根据回文串的结构特点,避免重复运算
p[i] = mx>i?Math.min(p[2*po - i], mx-i):1;
while(newstring.charAt(i - p[i]) == newstring.charAt(i + p[i]))
p[i]++;
//更新数据
if(p[i] + i > mx){
mx = p[i] + i;
po = i;
}
if(maxlen < p[i]){
maxpo = i;
maxlen = p[i];
}
//System.out.println(p[i]);
}
//System.out.println(anspo);
return s.substring(maxpo/2 - maxlen/2 , maxpo/2 + maxlen - maxlen/2 - 1);
}
}