Manacher(马拉车算法)
## 算法概述 - 用于对字符串中回文串相关的操作 - 如寻找最长回文串 - 时间复杂度 O(n)算法原理
-
example: str = "a film called tenet"
-
寻找最长回文串的一般解法(暴力)
对于字符串中的每一个字符 进行中心拓展
伪代码 时间复杂度O(n^2)for(int i=0;i<len;++i){ int l=r=i,tmp=-1; while(l>=0&&r<len&&str[l--]==str[r++]){ tmp+=2;}//字符串长度为奇数 ans=tmp>ans?tmp:ans; l=i,r=i+1,tmp=0; while(l>=0&&r<len&&str[l--]==str[r++]){ tmp+=2;}//字符串长度为偶数 ans=tmp>ans?tmp:ans; }
-
马拉车算法流程如下
-
预处理
先在字符串开头与结尾插入两个不同的特殊字符
再在字符之间插入特殊符号
如:" ^#a# #f#i#l#m# #c#a#l#l#e#d# #t#e#n#e#t#$ "
将字符串转换为奇数长度进行统一处理,首尾不同的符号帮助边界判断 -
算法核心
在暴力的同时,最大化利用已有信息,对后续操作提供加速
流程如下 :-
以每个字符为中心进行回文半径计算
str ^ # t # e # n # e # t # $ raduis 1 2 1 2 1 6 1 2 1 2 1 location 0 1 2 3 4 5 6 7 8 9 10 11 12 -
这里raduis用于记录对应位置的回文半径
把目光放在str[6] 这里计算出半径为6 (记center=6)
可以知道指针在运算时实际已经处理到了0和10的位置 (记最前位置R=12)
一般暴力操作是以下个字符str[7]为中心继续向两端进行匹配拓展
而马拉车进行的优化就是在这里
对于str[7] 7<12 断言raduis[7]=raduis[5]=1
对于str[8] 7<12 断言raduis[8]=raduis[4]=2 -
分析
5 4 3 2 1 0 # e # t # ^ n # e # t # $ 7 8 9 10 11 12 a.由对称性str[5]str[1]和str[7]str[11]是相同的两段
当计算raduis[7]和radius[8]时我们会注意它们和
对称的raduis[5]和radius[4]“大致”相同
仔细发现str[4]的回文串部分str[5]~str[3]
是包含在str[6]的回文串部分str[1]~str[11]的
而又由str[6]位置的回文性质(a.) 可以知道str[4]的回文串部分会对应在str[8]左右
即str[5]str[3]对应到str[7]str[9]
从而可以得到raduis[8]=raduis[4]
这就是马拉车算法用到的思想 -
其他情况的考虑
刚才是对应位置的回文串部分被包含在一个大的回文串里
即str[i]的 对应位置回文半径长 raduis[2C-i] + i < R (R是已知回文串最右位置)
对于raduis[2C-i] + i >= R 我们不知道溢出的部分 是否能匹配上
但可以知道至少有str[i]~ str[R]这一部分是对称的
也就是说raduis[i]数值上至少等于R - i
于是我们可以直接从溢出部分开始进行字符串比较 -
综合考虑
当正在计算的位置 i < R 的时候可以优化
raduis[2C-i] < R - i 的时候 raduis[i] = raduis[2C-i]
raduis[2*C-i] >= R - i 则raduis[i] = R - i 再从最远端位置R继续匹配 -
关于回文串返回
str ^ # t # e # n # e # t # $ raduis 1 2 1 2 1 6 1 2 1 2 1 location 0 1 2 3 4 5 6 7 8 9 10 11 12 -
原回文串的长度 len = raduis[i] - 1
这里不是巧合
raduis[i]*2-1得到预处理后的回文串长度 总是奇数
对应的回文串只有两种形式- #x#x# 原回文串长度为偶数
- #x#x#x# 原回文串长度为奇数
但不管如何'#'的个数总是比处理字符多1
(raduis[i] * 2 - 1 - 1) / 2得到正确长度 -
原回文串开始位置(包含) start = (i - raduis[i]) / 2
index/2得到原字符串位置+1的地方
i-radius[i]得到预处理串开始位置(不包含)
(i-radius[i])/2 得到原字符串开始位置(包含)
-
-
代码实现
class solution {
String manacher(String s) {
if (s.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder("^#");
for (char c : s) {
builder.append('c');
builder.append('#');
}
builder.append('$');
char[] str = builder.toString().toCharArray();
int len = str.length;
int[] raduis = new int[len];
int C = -1, R = -1;
int ans = 0;
for (int i = 1; i < len - 1; ++i) {
raduis[i] = i > R ? 1 : Math.max(R - i, raduis[2 * C - i]);
while (str[i - raduis[i]] == str[i + raduis[i]]) {
++raduis[i];
}
if (raduis[i] > raduis[ans]) {
ans = i;
}
if (i + raduis[i] > R) {
R = i + raduis[i];
C = i;
}
}
int len = raduis[ans] - 1;
int start = (ans - raduis[ans]) / 2;
return s.subString(start, start + len);
}
}