最近在刷LeetCode的时候遇到了Manacher’算法,这里来总结一下我对Maracher’s算法的理解
1. 算法由来
Maracher’s算法是为了解决最长回文子串的一种方法,这种方法将回文子串的时间复杂度降低到了(O(n))
2.预处理
为了减少处理奇回文和偶回文的麻烦,所以对原始字符串进行了处理。 例如:
abab //处理前的字符串
a#b#a#b//处理后的字符串
这样就避免了奇回文和偶回文的麻烦。
3.计算p数组
在这里,我们提出p数组的概念,p数组保存的是该位置的最长回文子串的半径。
接下来的问题是如何计算p数组。
答案很简单,从该位置开始向两边延伸。如果该位置两边的元素相等,则回文长度+1。这与求最长回文字符串的普通方法相同。
这里先提出两个概念,mx,目前通过回文子串计算能延伸到的
最远半径的最右位置以及id,及能达到最右位置的点
//最长回文字符串的中心位置索引
int index = 0;
for(int j = 1; j<n-1; j++) {
p[j] = mx > j ? Math.min(p[2 * id - j], mx - j) : 1;
//向左右两边延伸,扩展右边界
while (t.charAt(j + p[j]) == t.charAt(j - p[j])) {
p[j]++;
}
if(mx < p[j]+j){
mx = p[j]+j;
id = j;
}
}
但是不同之处在于,当求以点j为中心的回文半径时,如果在当前的最右位置内,可以通过对称的方式求得回文半径,不再需要重复的从开头开始。
同时,我们需要考虑边界情况。当越界的情况出现时,此时需要比较p[i] = Math.min(p[2*id - i], mx - i) 与mx-j的大小。
如果实在不懂建议手动模拟,手动模拟是理解算法最好的方式。
4. 计算最长回文子串长度
我们得到最长回文半径和最长回文子串长度之间的关系:int maxLength = p[i]-1。maxLength表示最长回文子串长度。
5.计算最长回文字符串的起始索引
了解了最长回文字符串的长度我们还需要知道它的起始索引值,这样才能截取出最完整的最长回文子串。由于偶回文会下标越界,因此需要对其处理保证其无论如何都是奇回文,解决方法很简单,在前面加一个$即可。这样就不会变成奇回文。同时,为了满足右边界条件,也要在右边加上一个@。
最后得到公式int index = (i - p[i])/2。
贴上完整代码结束
public static String Manachers(String s){
if(s.length()<2){
return s;
}
String t= "$";
for(int i = 0; i<s.length(); i++){
t+= "#"+s.charAt(i);
}
t += "#@";
//第二部,计算数组p
int n = t.length();
int[] p = new int[n];
int id = 0, mx = 0;
//最长回文字符串的长度
int maxLength = -1;
//最长回文字符串的中心位置索引
int index = 0;
for(int j = 1; j<n-1; j++) {
p[j] = mx > j ? Math.min(p[2 * id - j], mx - j) : 1;
//向左右两边延伸,扩展右边界
while (t.charAt(j + p[j]) == t.charAt(j - p[j])) {
p[j]++;
}
if(mx < p[j]+j){
mx = p[j]+j;
id = j;
}
}
//第三部,截取字符串,输出结果
int start = (index - maxLength)/2;
return s.substring(start, start+maxLength);
}