Manacher算法

今天思考一道題的時候,學習了一些思路,其中 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("");
相關文章
相關標籤/搜索