今天思考一道題的時候,學習了一些思路,其中 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("");