今天思考一道题的时候,学习了一些思路,其中 Manacher 算法颇有必要记录下来。
本文参考了:http://blog.csdn.net/ggggiqny...javascript
这道题的内容是:java
给定字符串,找到它的最长回文子串
最简单的思路莫过于找到给定字符串的全部子字符串,而后一个个的判断他们是不是回文字符串,在判断的时候用一个变量把最长的回文字符串记录下来就能够了;
判断是否是回文字符串很容易算法
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),太费时间;浪费时间的主要缘由是没有充分地利用得到的信息。学习
————————————————————分界线————————————————————————.net
Manacher算法很是巧妙,使用了一些辅助技巧使得整个算法的时间复杂度变为线性。
咱们先明确两件事:code
一个字符串是回文字符串,其中间位置为m。若他的子串S[i,i+x]为回文串,则相对于m对称的另外一端子串S[2m-i, 2m-(i+x)]必然是回文串。blog
回文串一定是中心对称的,也就是:S[i] == S[2m-i]。ip
首先,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("");