最长回文子串问题:给定一个字符串,求它的最长回文子串长度。
暴力解法:找到所有子串,验证是否是回文子串,时间复杂度是O(n^3)。
改进算法:遍历一遍字符串,求以每个字符为中心的回文子串长度,时间复杂度是O(n^2)。
用Manacher来解决最长回文子串问题,算法复杂度是O(n)。
Manacher算法属于动态规划,但是如果认为这样的算法才是动态规划,是对动态规划的误解,还会打击学习的积极性,动态规划只是在分治的基础上做了一些优化,《算法导论》里边讲的很好,也可以参考之前的一篇文章:动态规划。
参考:
https://www.cnblogs.com/grandyang/p/4475985.html
https://segmentfault.com/a/1190000003914228
https://segmentfault.com/a/1190000008484167
http://www.csie.ntnu.edu.tw/~u91029/index.html

算法利用回文子串左右对称的特性。
如上图,从左往右求每一个字符的回文子串,如果我们已经算过以pos为中心的回文子串,并记录下改回文子串的最右端maxR,当我们求之后的以i为中心(i<maxR)的回文子串的时候,可以直接读取i相对于pos的对称点的回文子串长度,如果i加上这个长度小于maxR的话,那么以i为中心的回文子串长度就是这个值,如果i加上这个长度大于等于maxR,那么以i为中心的回文子串长度至少是maxR-i,然后只需要判断以i为中心maxR以后的字符,之后pos更新为i,maxR更新为以i为中心的回文子串的最右端。如果i大于maxR的话,赋初始值,向后计算即可。
如果 maxR > i, 则 p[i] = min( p[2 * pos - i] , maxR - i )
否则,p[i] = 1
实际写代码的时候,用到了一些技巧,简化了代码量。当然,按照思路写if~else也没有问题。
manacher('122122');
manacher2('122122');
manacher('abbahopxp');
manacher2('abbahopxp');
function manacher(s){
var a = ['#'];
for(var i = 0; i < s.length; i++) {
a.push(s.charAt(i));
a.push('#');
}
var maxR = 0;
var pos = 0;
var resLen = 0;
var resCenter = 0;
var p = [];
for(var i = 0; i < a.length; i++) {
p[i] = maxR > i ? Math.min(p[2 * pos - i], maxR - i) : 1;
while(a[i + p[i]] === a[i - p[i]]) {
p[i]++;
}
if(maxR < i + p[i]) {
maxR = i + p[i];
pos = i;
}
if(resLen < p[i]) {
resLen = p[i];
resCenter = i;
}
}
console.log('---------manacher start----------');
console.log(s);
console.log(a.join());
console.log(p.join());
console.log(resCenter, resLen);
console.log(s.substr(Math.floor((resCenter + 1 - resLen) / 2), resLen - 1));
console.log('---------manacher end----------');
}
function manacher2(s) {
var a = ['#'];
for(var i = 0; i < s.length; i++) {
a.push(s.charAt(i));
a.push('#');
}
var pos = 0;
var maxR = 0;
var p = [];
for(var i = 0; i < a.length; i++){
if (i < maxR) {
var L = p[2*pos - i];
if(i + L < maxR) {
p[i] = L;
} else {
p[i] = maxR - i;
pos = i;
while(a[i + p[i]] === a[i - p[i]]){
p[i]++;
}
maxR = i + p[i];
}
} else {
p[i] = 1;
pos = i;
while(a[i + p[i]] === a[i - p[i]]){
p[i]++;
}
maxR = i + p[i];
}
if (maxR === a.length) {
break;
}
}
console.log('---------manacher2 start----------');
console.log(s);
console.log(a.join());
console.log(p.join());
var mi = 0;
for(var i = 1; i < p.length; i++) {
if(p[i] > p[mi]) {
mi = i;
}
}
console.log(mi, p[mi]);
console.log(s.substr(Math.floor((mi + 1 - p[mi]) / 2), p[mi] - 1));
console.log('---------manacher2 end----------');
}先对字符串进行处理,插入一个原始字符串中不存在的字符(为了统一奇偶的情况,详见参考文章)。比如‘12321’插入‘#’后变成‘#1#2#3#2#1#’。
初始化变量i=0,pos=0,maxR=0,p=[]。(p用来存储计算结果)
计算以i为中心的回文子串长度。
如果 maxR > i, 则 p[i] = min( p[2 * pos - i] , maxR - i ) ;否则,p[i] = 1
继续向右扩展计算p[i]
如果i+p[i]>maxR,更新maxR和pos
如果i是字符串的结尾,结束,否则i++,跳转到3。
通过p找到最大回文子串的位置和长度。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。