本文将通过Manacher算法来寻找最长回文子串。该算法O(N)时间复杂度为O(N),空间复杂度为O(N)。
一种O(N)的解法(Manacher算法)
首先,我们通过插入特殊字符#将输入字符串S转换为字符串T。具体原因本文随后会进行解释。
例如: S = “abaaba”, T = “#a#b#a#a#b#a#.
为了找出最长回文子串,我们需要对每个Ti扩展,这样Ti-d…Ti+d将会构成回文。d是以Ti为中心的回文串的长度。
我们将中间存过存储在数组P中,P[i]为以Ti为中心的回文串的长度。最长回文子串长度是P[i]中的最大值。
以 S = “abaaba”,最后T与P如下。
T = # a # b # a # a # b # a #
P = 0 1 0 3 0 1 6 1 0 3 0 1 0
通过数组P我们可以看到P6=6,最长回文是”abaaba”。
你注意到了插入在字符间的特殊字符#后,奇数长度和偶数长度的回文都以同样的方式处理?
现在,想象在回文串abaaba中心画一条垂线。你是否注意到了在中心附近的数字都是对称的?这种特性不仅存在于abaaba中,对于aba,同样具有这样的对称属性。这是巧合么?即是又不是,这种特性需要满足一个条件,但无论何,我们已经取得了很大的进步,因为我们可以消除P[i]的重复计算。
接下来看一个略微复杂的例子,这个例子有多个重叠回文,S = “babcbabcbaccba”.
上图中T通过S插入#变换得到。假定,目前已经计算出数组P的部分数值。实现是回文abcbabcba的中心。两条虚线是回文的两个边界。现在在位置i,如何有效计算出P[i]?
假设现在 i = 13,并计算P[13],首先i的镜像位置i’,i’=9。
两条绿色实线表示以i与i’为中心的两个回文锁覆盖的区域。P[i`] = P[9] = 1。很明显由于对称性,P[i]也是1。
如上,由于对称性P[i] = p[i`] = 1。实际上,其余3个位置都满足对称性。(P[ 12 ] = P[ 10 ] = 0, P[ 13 ] = P[ 9 ] = 1, P[ 14 ] = P[ 8 ] = 0).
现在,i = 15。P[i]等于什么?如果按照对称的特点算,那么P[i] 应该等于P[i’] = 7。但这是错的。如果在T15处扩展,形成的回文是a#b#c#b#a。这个回文比其对应的回文短?这是为什么?
着色的线段覆盖了以i,i’为中心的回文。绿色实线覆盖的区域是对称的。而红色实线,是两个并不一定匹配的部分。而绿色虚线是穿越中心C的部分。
显然,两个在绿色实线锁覆盖的区域内的子串一定匹配。而跨越中心C的部分(由绿色虚线覆盖)的部分也是对称的。注意,P[i’]是7,该子串左半部分越过了左边界。因此不再满足回文对称属性。我们知道P[i] >= 5,为了找出P[i]的真实值,我们需要进行通过向右边界R进行扩展来完成字符串匹配。这种情况下,P[21] != P[1],我们得出P[i] = 5。
算法的关键部分如下。
if P[ i’ ] ≤ R – i,
then P[ i ] ← P[ i’ ]
else P[ i ] ≥ P[ i’ ]. (向右边界扩展,通过字符串匹配,找出P[i]).
最后,需要确定什么时候移需要向右动回文中心C与R,这很容易。
如果回文中心在位置i,而且没有超越R(右边界),那么以将C更新为i(新回文的中心),而R更新为新回文的右边界。
在每步中,有两种可能, If P[ i ] ≤ R – i, P[ i ] = P[ i’ ]。否则,进行字符串匹配来计算P[i]。计算时扩展右边界最多需要N步,而计算位置P最多N步。因此该算法确保在2*N步完成。
Java实现代码:
import java.util.*;
public class Solution {
public String longestPalindrome(String s) {
String T = preProcess(s);
int n = T.length();
int p[] = new int[n];
int C = 0,
R = 0;
for(int i = 1; i <= n-1; i++) {
int i_mirror = 2*C - i; // 镜像位置
if(R > i) {
if(R - i >= p[i_mirror]) {
p[i] = p[i_mirror];
} else {
p[i] = R - i; // 后续扩展计算
}
} else {
p[i] = 0; // i == R 当前i位于#号位置,所以p[i] = 0, i > R,超过原半径,初始为0,需要扩展计算
}
while(i+1+p[i] < n && i-p[i]-1 >= 0 && T.charAt(i+1+p[i]) == T.charAt(i-p[i]-1)) {
p[i]++;
} // 进行字符串匹配,看对称点是否相等
if(i + p[i] > R) {
C = i;
R = i + p[i];
}
}
int maxLen = 0;
int centerIndex = 0;
for(int i = 1; i < n-1; i++) {
if(p[i] > maxLen) {
maxLen = p[i];
centerIndex = i;
}
} // 得到最大回文半径以及回文中心。
//System.out.println(maxLen + " " + centerIndex);
StringBuilder sb = new StringBuilder();
for(int i = centerIndex - maxLen; i <= centerIndex + maxLen; i++) {
if(T.charAt(i) != '#') {
sb.append(T.charAt(i));
}
}
return sb.toString();
}
public String preProcess(String s) {
int n = s.length();
if(n == 0) {
return "^$";
}
String ret = "^";
for(int i = 0; i < n; i++){
ret += "#" + s.substring(i,i+1);
}
ret += "#$";
return ret;
}
}
public class OjMain {
public static void main(String[] args) {
Solution s = new Solution();
String pa = "babcbabcbaccba";
System.out.println(s.longestPalindrome(pa));
}
}
原文地址:
http://articles.leetcode.com/longest-palindromic-substring-part-ii/