发现自己时间过久了就会忘掉 ... 写一遍在这里吧~
问题: 求一个字符串的最长回文子串.
直观思路, 每一个回文子串都可以由"中心"和"半径"来唯一决定, 如回文串"abcdbca"的中心是c, 半径长度为3. (对于总长度为偶数的回文子串, 我们先想象其中心不是一个字符, 而是字符串中间的"间隙"; 以下讨论中, 为便于分析, 暂时只考虑奇数长度的回文串)
按这个思路, 我们已经可以解决这个问题了: 对原字符串的每个字符, 都把它当作中心来求最大半径. 求最大半径的过程就是一个试图拓展的过程, 不断比较c-r和c+r处的字符串是否相同.
while (s[c+r] == s[c-r]) {
r = r + 1;
}
在此基础上, 我们如果考虑到, 对于已知的回文串, 其中心的左右两边是对称的, 那么就可以发现有些字符比较是不必要的.
如图, 对于字符串S, 在我们从左至右依次把每个位置都当作中心拓展最长回文子串的过程中, 假设绿色部分是已知的回文串, 现在考虑中心C2. 那么, 注意到以C为中心的绿色左右两边的对称的. 我们可以关注一下C2的对称点C1, 如果C1的半径是r1, C2的半径至少也是r1, 可以节省r1次比较.
// maxCover:最右边界 maxC:最右边界对应的中心 r[]:每个点作为中心的半径
curR = 1;
if (C2 < maxCover) {
C1 = maxC - (C2 - maxC);
r1 = r[C1];
curR = min(r1, maxCover - C2);
}
while (s[C2 - curR] == s[C2 + curR]) {
curR = curR + 1;
}
curR = curR - 1;
// 维护 maxCover 等
最后, 为了避免奇偶性的讨论(也就是中心是字符还是字符之间的间隙), 以及省去对字符串边界的条件判断, 对原字符串进行一次预处理: 在每两个字符中间都插入一个原串中未出现过的特殊字符, 并在字符串开头另加一个特殊字符.
如插入'#'和'@'将"abaa"变为"@a#b#a#a".
这就是Manacher算法.