最长回文子串问题,网上看O(n)的算法都是马拉车算法,但是很多讲的不够透彻。找了一圈,这篇是我唯一完全看懂了的:
Manacher’s Algorithm Explained— Longest Palindromic Substring
下面按照自己的理解,阐述一遍,算是巩固吧。
一、处理字符串长度有奇有偶的问题
对原字符串做预处理,每两个字符中间的空位,插入一种特殊字符,例如#。另外,头尾也插入这种特殊字符。
原来奇数长度的串,如aba,插入后#a#b#a#
原来偶数长度的串,如acca,插入后#a#c#c#a#
原字符串长n,插入了n+1个#,总长2n+1,恒为奇数,这样就解决了奇偶需要分类处理的问题。
二、回文串中的镜像概念
如上图示,c为回文串中心,l为其左边界,r为右边界。
此时称以c为中心的回文子串的回文半径为r-c
由回文串的镜像性质,可知每一个在[c,r]区间内的点j,都会有一个镜像点j',落在[l,c]区间内。
三、建立辅助数组P[]
P[i]记录以i为中心的最长回文子串的回文半径。
设点i在[c,r]区间内,其镜像点i' = 2*c - i。因为P[]的计算是从前到后的,此时的P[i']已计算完成。
根据以i'为中心的回文串的左边界是否超过l,分两种情况讨论:
1)以i'为中心的回文串左边界没有超过l,则P[i]的最小可能值是P[i']。
2)以i'为中心的回文串左边界超过了l,则P[i]的最小可能值是r-i。
这里用反证法说明:如果以i'为中心的回文串左边界超过了l,而r+1位依然以i为中心形成回文的话,那么根据镜像性,第l-1位将以i'为中心形成回文,且满足str[l-1] == str[r+1]。如此则以c为中心的最长回文串的区间应该扩大为[l-1, r+1],与假设的[l,r]相矛盾,故不可能。
string manacher(string s) {
const int n = s.size();
if (!n)
return s;
string ss = "#";
for (int i = 0; i < n; ++i) {
ss += s[i];
ss += '#';
}
const int N = ss.size();
vector<int> P(N, 0);
int c = 0, l = 0, r = 0;
int maxLen = 0;
for (int i = 1; i < N; ++i) {
// ii is the mirror of i with the center c
int ii = 2 * c - i;
if (i < r) {
P[i] = min(ii >= 0 ? P[ii] : 0, r - i);
}
else {
P[i] = 0;
}
//il, ir is the bourndary of the longest panlindrome substr centered at i
int il = i - P[i] - 1, ir = i + P[i] + 1;
while (il >= 0 && ir < N && ss[il] == ss[ir]) {
++P[i];
--il;
++ir;
}
// length of the longest palindrome substr centered at i
int len = 2 * P[i] + 1;
if (len > maxLen) {
maxLen = len;
c = i;
l = c - P[i], r = c + P[i];
}
}
if (ss[l] == '#')
++l;
if (ss[r] == '#')
--r;
// char at i in ss is at i/2 in s
int left = l / 2, right = r / 2;
return s.substr(left, right - left + 1);
}
LeetCode测试通过。