给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000.
示例1:
输入:“babad”
输出:“bab”
注意:"aba"也是一个有效答案。
示例2:
输入:“cbbd”
输出:“bb”
首先,把输入的字符串 S 转换成另一个字符串 T,转换方法是在字符中间插入字符 #。
例如:S = “abaaba”, T = “#a#b#a#a#b#a#”。
为了发现最长回文子串,我们需要从每个 T i T_i Ti 开始拓展,拓展后的子串 T i − d . . . T i + d T_{i-d}...T_{i+d} Ti−d...Ti+d 形成回文。可以知道, d d d 是以 T i T_i Ti 为中心的回文的长度。
我们利用数组 P 存储每个以 T i T_i Ti 为中心的回文的长度。最长回文子串长度是数组 P 中最大值。
利用上述例子,从左到右填充数组 P:
T = # a # b # a # a # b # a #
P = 0 1 0 3 0 1 6 1 0 3 0 1 0
通过数组 P,我们可以得到最长回文子串是 “abaaba”。数组 P 的最大值: P 6 = 6 P_6 = 6 P6=6。
现在,想象在回文 “abaaba” 的中心划一条想象的垂线。数组 P 中的值关于这条线对称。同样回文 “aba” 也是这样。这个对称性质是在满足一定条件下才成立。
接下来,我们讨论一个更复杂的例子,这个字符串包含一些重叠的回文。例子如下:
上图中,将字符串 S = “babcbabcbaccba” 转换为 T。此时数组 P 已经完成一部分。实线表明回文 “abcbabcba” 的中心 C。两条虚线表明相对于中心的左边(L)和右边®。指数 i i i 与 i ′ i\prime i′ 是关于中心 C 对称。
当指数 i = 13 i=13 i=13 时,如果计算 P [ 13 ] P[13] P[13],应该看关于回文中心 C 对称的指数 i ′ i\prime i′,此时 i ′ = 9 i\prime =9 i′=9。
上图中,绿色实线覆盖区域表示两个以 i i i 和 i ′ i\prime i′ 为中心的回文。 i i i 和 i ′ i\prime i′ 是关于中心 C 的镜像指数。根据对称性, P [ i ] = P [ i ′ ] = 1 P[i]=P[i\prime]=1 P[i]=P[i′]=1,即 P [ 13 ] = 1 P[13]=1 P[13]=1。
现在,计算 P [ 15 ] P[15] P[15]。如果仍然依据对称性(之前说过对称性在一定条件下才能满足), P [ 15 ] = P [ 7 ] = 7 P[15]=P[7]=7 P[15]=P[7]=7。但这显然不对。如果我们以 T 15 T_{15} T15 为中心拓展,仅能形成 “a#b#c#b#a” 回文,这比按照对称性得到的长度要短。
如上图所示,依据 C 为中心的对称性,绿色实线表示两侧必须匹配的区域。红色实线表示两侧可能不匹配的区域。绿色虚线表示穿过中心的区域。
很明显,由两条绿色实线表示区域中的两个子字符串必须完全匹配。穿过中心的区域(由绿色虚线表示)也是对称的。注意, P [ i ′ ] = 7 P[i\prime]=7 P[i′]=7,它向左拓展越过回文的左边界(L),所以它不再符合回文的对称性质。我们现在知道 P [ i ] ≥ 5 P[i]\ge 5 P[i]≥5,为了确定 P [ i ] P[i] P[i] 的值,必须向右拓展超过右边界®进行字符匹配。因此,可以知道 P [ 21 ] ≠ P [ 1 ] P[21]\ne P[1] P[21]̸=P[1],结论是 P [ i ] = 5 P[i]=5 P[i]=5。
算法的关键步骤如下。
i f P [ i ′ ] ≤ R − i , t h e n P [ i ] ← P [ i ′ ] e l s e P [ i ] ≥ P [ i ′ ] . if\ P[i\prime]\le R-i,\\ then\ P[i]\gets P[i\prime]\\ else\ P[i]\ge P[i\prime]. if P[i′]≤R−i,then P[i]←P[i′]else P[i]≥P[i′].
最后一步是确定如何同时移动 C 和 R 的位置。方法如下:
如果以 i i i 为中心的回文向右拓展穿过 R,更新 C 为 i i i,并将 R 拓展到新回文的右边。
复杂度分析
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; //equals to i = C - (i - C)
p[i] = (R > i) ? min(R - i, p[i_mirror]) : 0;
// Attempt to expand palindrome centered at i
while(T[i + 1 + p[i]] == T[i - 1 - p[i]])
p[i]++;
// If palindrome centered at i expand past R,
// adjust center based on expanded palindrome
if(i + p[i] > R) {
C = i;
R = 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;
}
}
delete[] p;
return s.substr((centerIndex - 1 - maxLen) / 2, maxLen);
}
// Transform S into T
// ^ and $ signs are sentinels appended to each end to avoid bounds checking
string preProcess(string s) {
int n = s.length();
if(!n) return "^$";
string ret = "^";
for(int i = 0; i < n; i++)
ret += "#" + s.substr(i, 1);
ret += "#$";
return ret;
}
};
@ 山东·威海 2019.01.30