Manacher's algorithm(马拉车算法)
这是对于上一篇的最长回文子串的延伸,之前的算法最好的实现的时间复杂度都是O(n^2),这个算法直接改进至了O(n),可以说是非常牛了。
我先贴上算法源网页,写的很详细,有图有步骤,不过.....是英文的。英文基础不错的建议出门左转直接看大佬的解释:https://articles.leetcode.com/longest-palindromic-substring-part-ii/
把Manacher algorthm音译成马拉车算法,虽然说有点土,但还是很形象的。有道是,好马不吃回头草,所以这个也是一次从左往右的遍历就解决了问题。但是我还是自己给它命名为镜像算法。
算法的思路很简单:
1.Combine odd number and even number
我们知道,回文串可能是ABC式,或者ABBA式,这就很麻烦,需要考虑两者。算法通过在每两个字符间、头尾添加字符#,就可以解决。现在变成#A#B#A#和#A#B#B#A#式,最后忽略字符#即可。
假设经过 1 处理 后,字符串为T
2.avoid unnecessary computation
避免重复计算的工作是算法的重中之重,下面的篇幅都用于解释这一块。
(1) 首先,我们引入int数组 P[ ],P的长度为T.length()
P[i]=x 表示:以i为中心的回文子串的右边尽头离中心i的距离为x.
比如说,T=# A # B # A #
则:P=0 1 0 3 0 1 0
(2)我们来看看马是怎么拉车的,也就是算法到底避免了哪些重复计算:
比如说,第一种情况:
T=# b # a # b # c # b # a # b # e
P[i]=0 1 0 1 0 1 0 7.......?
现在的问题是,后面的P[i]如何完善,我们来看对应的回文串中心是c,即P[i]=7的地方。
由对称性可知,c的左边的7个字符和右边的7个字符是镜像对称的,那么对称的点的P值应该就是一样的吧?
这么说不完全正确,不过大部分情况确实如此,这部分的P值不必重复计算。
我们来看特殊情况:
T=....a # B # a # b # c # b # a # B # e
P[i]=....1 0 5 0 1 0 1 0 7.......?
现在的问题是,后面的P[i]如何完善,我们来看c右边的B处,B对称的是左边的B,那么是不是P值就是5呢?
当然,不是!!!
这个时候,右边的B值就要重新暴力计算了。
(3)等马车到终点之后,检查一下P,找出最大值就得到了结果。
附上java代码:
package pac;
public class solution3 {
//insert # into s between letters
public String preProcess(String s){
int n = s.length();
if (n == 0) return "^$";
String ret = "^";
for (int i = 0; i < n; i++)
ret += "#" + s.charAt(i);
ret += "#$";
return ret;
}
//solve the problem
public String longestPalindrome(String s) {
String T = preProcess(s);
int n = T.length();
int[] P = new int[n];
int Center = 0, Right = 0;
//fill the p[]
for (int i = 1; i < n-1; i++) {
int i_mirror = 2*Center-i; // equals to i' = Center - (i-Center)
P[i] = (Right > i) ? Math.min(Right-i, P[i_mirror]) : 0;
// Attempt to expand palindrome centered at i
while (T.charAt(i + 1 + P[i]) == T.charAt(i - 1 - P[i]))
P[i]++;
// If palindrome centered at i expand past R,
// adjust center based on expanded palindrome.
if (i + P[i] > Right) {
Center = i;
Right = i + P[i];
}
}
// Find the maximum element in P.
int maxLen = 0;
int centerIndex = 0;
for (int i = 1; i < n-1; i++) {
if (P[i] > maxLen) {
maxLen = P[i];
centerIndex = i;
}
}
return T.substring(centerIndex-maxLen,centerIndex+maxLen+1);
}
/**
* Test unit
* @param args
*/
public static void main(String[] args) {
solution3 x=new solution3();
String s="yabczcbax";
String result=x.longestPalindrome(s);
System.out.println(result);
}
}