本日思索一道题的时刻,进修了一些思绪,个中 Manacher 算法很有必要记录下来。
本文参考了:http://blog.csdn.net/ggggiqny…
这道题的内容是:
给定字符串,找到它的最长回文子串
最简朴的思绪莫过于找到给定字符串的一切子字符串,然后一个个的推断他们是不是是回文字符串,在推断的时刻用一个变量把最长的回文字符串记录下来就能够了;
推断是不是是回文字符串很轻易
function isPalindrome(str) {
var newStr = str.split("").reverse().join("");
return newStr === str ? true : false;
}
取得一切子串也很轻易
function getSubstring(str){
var len = str.length;
for(var i=0; i<len; i++){
for(var j=i; j<len;j++){
console.log(str.substring(i,j+1));
}
}
}
这类简朴粗犷的算法带来的效果就是:查找子串时候复杂度O(n^2),推断回文时候复杂度O(n),太费时候;浪费时候的重要原因是没有充分地应用取得的信息。
————————————————————分界线————————————————————————
Manacher算法异常奇妙,运用了一些辅佐技能使得全部算法的时候复杂度变成线性。
我们先明白两件事:
一个字符串是回文字符串,个中间位置为m。若他的子串S[i,i+x]为回文串,则相关于m对称的另一端子串S[2m-i, 2m-(i+x)]必定是回文串。
回文串必定是中间对称的,也就是:S[i] == S[2m-i]。
起首,Manacher算法运用了以下的一个技能让我们不必斟酌字符串的奇偶性问题:
每个字符双方都加上一个特别字符,比方以字符串”abba”为例,转换后变成”#a#b#b#a#”。这样一来字符串不管本来是奇数照样偶数,都邑变成奇数。
function getNewString(str){
var newStr = '#';
for(i = 0;i<len;i++){
newStr += str[i]+'#';
}
}
然后设置了一个观点:建立一个新数组P, P[i]项示意以第i个字符为中间的回文字串的半径。比方
S # a # b # b # a #
P 1 2 1 2 5 2 1 2 1
经由过程表格能够发明,P[i]-1就是现实回文字串的长度(对应的是标记照样数字都没关联)。
所以我们的使命转化为了求解数组 P;
求解数组 P 是本算法中心,依据我的明白,将其归纳综合为以下:
设置两个辅佐参数:id 和 mx;id示意当前已纪录过的边疆最大的回文字符串的中间位置,mx此回文字符串的边疆值,也就是id+p[i]
;
初始化一便数组P,以防止数组中有undefined
:
for(i = 0;i<newLen;i++){
p[i] = 0;
}
接下来最先议论:
记 i 对应于中间点 id 的对应位置为j,即j = 2*id - i
;
若当前已纪录的最大边疆 mx > i(即 i 位置对应的字符在已知回文字符串内),那末:
p[i] = Math.min(p[j], mx-i);
就是当前面比较的最远长度 mx > i 的时刻,P[i]有一个最小值,这就是本算法最中心的性子。
现在肯定的P[i]是回文半径范围内能肯定的值,关于半径外的字符,由于不知能可否和已知回文串继承构成更大回文串,所以也要举行推断。
while ((newStr[i + p[i]] == newStr[i - p[i]]) && newStr[i + p[i]]){
p[i]++;
}
末了一步,当有更大的回文串出现时,更新mx 和 id 的值
if (i + p[i] > mx) {
id = i;
mx = id + p[i];
}
团体代码
function getArrayP(str){
var p = [],
mx = 0,
id = 0;
var i;
var newStr = '#'; // 将字符串转化为奇数长度获取到新的字符串
var len = str.length;
for(i = 0;i<len;i++){
newStr += str[i]+'#';
}
var newLen = newStr.length;
for(i = 0;i<newLen;i++){
p[i] = 0;
}
for (i = 0;i < newLen; i++) { // 获取到一切的子回文的长度值构成的数组
p[i] = mx > i ? Math.min(p[2*id-i], mx-i) : 1;
while ((newStr[i + p[i]] == newStr[i - p[i]]) && newStr[i + p[i]]){ // 超越其半径的位置再做分外推断,确保 newStr[i + p[i]] 是存在的
p[i]++;
}
// 当有更大的回文串出现时,更新中间位置和最大边疆值
if (i + p[i] > mx) {
id = i;
mx = id + p[i];
}
}
return p;
}
取得数组 p 以后,我们就获取到P的最大值,上面的例子中,最大值是 p[4] = 5;由于回文半径算了本身在内,所以要减去1,所以回文字符串应该是从newStr[4-4]起,到newStr[4+4]为止。用newStr.subString(0,8)
要领取得字符串后,再去掉『#』标记就能够了;
newstr.subString(0, 8).split('#').join("");