@LeetCode最长回文子串--Longest Palindromic Substring[C++]
问题描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000.
示例1:
输入:“babad”
输出:“bab”
注意:"aba"也是一个有效答案。
示例2:
输入:“cbbd”
输出:“bb”
解决方法及复杂度分析
Manacher算法
首先,把输入的字符串 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 拓展到新回文的右边。
复杂度分析
- 时间复杂度:
O
(
N
)
O(N)
O(N)
– 扩展R(内部while循环)最多需要N个步骤,定位和测试每个中心也需要总共N个步骤。因此,该算法保证最多完成2 * N步,给出线性时间解。
程序实现
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