看了很多网上的解析,都是只讲解了过程,没有详细讲到为什么,开始看的时候容易云里雾里。Manacher 算法的核心思想是利用前面已经用中心扩散法寻找的回文子串的对称性来加速,也可以说是有点动态规划的意思,利用之前得到子串信息来确定当前的信息。
因为回文子串可能的长度可能是单数也可能是双数,为了统一起来,就在原串的开头和结尾以及每个字符之间添加上“#”,这个很好理解,就不再详细讲解。先设定一些变量:
s:加了“#”的字符串
i:当前字符串的位置
right:目前找到的回文子串的最远右边界(这个回文子串的右边界最大)
left:上诉回文子串的左边界
j:最远有边界回文子串的中心位置(以位置j为中心的回文子串的右边界最远,也就是right)
i_sym:i 点关于 j 点的对称点
arm_len[]:以各个字符为中心的回文子串的臂长(=(回文子串长度-1)/2)
cur_arm_len:以当前字符为中心的回文子串的臂长
** 算法原理:**
分成两大情况:
(1) right < i
此情况中,i已经超出了回文子串的最右边界,那就不能利用之前回文子串的对称信息,只能对点 i 用中心扩散法重新做计算。
(2) right >= i
这时候 i 是处于以 j 为中心的回文子串的范围内的,i_sym 是 i 关于 j 的对称点。这时又要分成三种子情况:
2.1 以 i_sym 为中心的回文子串的左边界大于 left,即 > left
因为回文子串的对称性,故在 left ~ riht 范围内, i 附近的字符和 i_sym 附近的字符是一样的。所以以 i 为中心的回文子串和以 i_sym 为中心的回文子串是一模一样的,所以臂长也一样,即 arm_len[i] = arm_len[i_sym]
2.2 以 i_sym 为中心的回文子串的左边界等于 left,即 = left
因为此情况不确定 left 是否为 s 的最初点,所以不能确定 left 左边的情况。所以需要对 i点用中心扩散法来计算,但只需要从 right 开始就可以,然后再视情况更新 j 和 right
2.3 以 i_sym 为中心的回文子串的左边界超出 left,即 < left
设 left 关于 i_sym 的对称点为 left_sym;right 关于 i 的对称点为 right_sum。
1)由于 left-1 和 left_sym+1 都在以i_sym 为中心的回文子串范围内,且中心对称,则s[left-1] = s[left_sym+1],
2)由于 left_sym+1 和 right_sym-1 是关于点 j 对称的,所以 s[left_sym+1] = s[right_sym-1]。
3)因为以 j 为中心的回文子串的左右边界是 left 和 riht,所以 left 左边的字符和 riht 右边的字符必然不相等,即 s[left-1] != s[right+1],
由 1)2)3)可得 s[right_sym-1] != s[right+1],也就是以 i 为中心的回文子串的边界只能是 right_sym 和 right+1,即臂长为 right - i。
这三种子情况都可以利用到之前回文子串的对称信息,节省了很多重复计算。代码中可以把三种情况统一起来,算出 min_arm_len = min(arm_len[i_sym], right – i),然后再从 i + min_arm_len 开始往外扩散。
*** 代码 ***
class Solution {
public:
int expand(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
--left;
++right;
}
return (right - left - 2) / 2;
}
string longestPalindrome(string s) {
int start = 0, end = -1;
string t = "#";
for (char c: s) {
t += c;
t += '#';
}
t += '#';
s = t;
vector<int> arm_len;
int right = -1, j = -1;
for (int i = 0; i < s.size(); ++i) {
int cur_arm_len;
if (right >= i) {
int i_sym = j * 2 - i;
int min_arm_len = min(arm_len[i_sym], right - i);
cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
} else {
cur_arm_len = expand(s, i, i);
}
arm_len.push_back(cur_arm_len);
if (i + cur_arm_len > right) {
j = i;
right = i + cur_arm_len;
}
if (cur_arm_len * 2 + 1 > end - start) {
start = i - cur_arm_len;
end = i + cur_arm_len;
}
}
string ans;
for (int i = start; i <= end; ++i) {
if (s[i] != '#') {
ans += s[i];
}
}
return ans;
}
};