1. 程式人生 > >leetcode演算法題5:最長迴文子串

leetcode演算法題5:最長迴文子串

題目:給定一個字串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度為 1000。

示例 1:輸入: "babad" ;輸出: "bab" 注意: "aba" 也是一個有效答案。

示例 2:輸入: "cbbd" ;輸出: "bb"

解析:

方法一:暴力法,先選出所有子字串,再檢驗它是不是迴文,方法的時間複雜度為O(n^3),空間複雜度為O(1)。

 var longestPalindrome=function(s)
 {
     if(s.length==0){ 
       return "";
     }
     if(s.length==1){
       return s;
     } 
     var start=0,maxlength=1;//記錄最大回文子串的起始位置以及長度
     for(var i=0;i<s.length;i++)
         for(var j=i+1;j<s.length;j++)//從當前位置的下一個開始算
         {
             var temp1,temp2;
             for(temp1=i,temp2=j;temp1<temp2;temp1++,temp2--)
             {
                 if(s[temp1]!=s[temp2])
                     break;
             }
             if(temp1>=temp2 && j-i+1>maxlength)//這裡要注意條件為temp1>=temp2,因為如果是偶數個字元,相鄰的兩個經上一步會出現大於的情況
             {
                 maxlength = j-i+1;
                 start=i;
             }
         }
    return s.substr(start,maxlength);//利用string中的substr函式來返回相應的子串,第一個引數是起始位置,第二個引數是字元個數
 }

方法二: Manacher法

這是一個專門用作處理最長迴文子串的方法,思想很巧妙,比較難以理解,這裡直接借用了別人的講解方法。其實主要思想是,把給定的字串的每一個字母當做中心,向兩邊擴充套件,這樣來找最長的子迴文串,這個叫中心擴充套件法,但是這個方法還要考慮到處理abba這種偶數個字元的迴文串。Manacher法將所有的字串全部變成奇數個字元。

Manacher演算法原理與實現

下面介紹Manacher演算法的原理與步驟。

首先,Manacher演算法提供了一種巧妙地辦法,將長度為奇數的迴文串和長度為偶數的迴文串一起考慮,具體做法是,在原字串的每個相鄰兩個字元中間插入一個分隔符,同時在首尾也要新增一個分隔符,分隔符的要求是不在原串中出現,一般情況下可以用#號。下面舉一個例子:

(1)Len陣列簡介與性質

Manacher演算法用一個輔助陣列Len[i]表示以字元T[i]為中心的最長迴文字串的最右字元到T[i]的長度,比如以T[i]為中心的最長迴文字串是T[l,r],那麼Len[i]=r-i+1。

對於上面的例子,可以得出Len[i]陣列為:

Len陣列有一個性質,那就是Len[i]-1就是該回文子串在原字串S中的長度,至於證明,首先在轉換得到的字串T中,所有的迴文字串的長度都為奇數,那麼對於以T[i]為中心的最長迴文字串,其長度就為2*Len[i]-1,經過觀察可知,T中所有的迴文子串,其中分隔符的數量一定比其他字元的數量多1,也就是有Len[i]個分隔符,剩下Len[i]-1個字元來自原字串,所以該回文串在原字串中的長度就為Len[i]-1。

有了這個性質,那麼原問題就轉化為求所有的Len[i]。下面介紹如何線上性時間複雜度內求出所有的Len。

(2)Len陣列的計算

首先從左往右依次計算Len[i],當計算Len[i]時,Len[j](0<=j<i)已經計算完畢。設P為之前計算中最長迴文子串的右端點的最大值,並且設取得這個最大值的位置為po,分兩種情況:

第一種情況:i<=P

那麼找到i相對於po的對稱位置,設為j,那麼如果Len[j]<P-i,如下圖:

那麼說明以j為中心的迴文串一定在以po為中心的迴文串的內部,且j和i關於位置po對稱,由迴文串的定義可知,一個迴文串反過來還是一個迴文串,所以以i為中心的迴文串的長度至少和以j為中心的迴文串一樣,即Len[i]>=Len[j]。因為Len[j]<P-i,所以說i+Len[j]<P。由對稱性可知Len[i]=Len[j]。

如果Len[j]>=P-i,由對稱性,說明以i為中心的迴文串可能會延伸到P之外,而大於P的部分我們還沒有進行匹配,所以要從P+1位置開始一個一個進行匹配,直到發生失配,從而更新P和對應的po以及Len[i]。

第二種情況: i>P

如果i比P還要大,說明對於中點為i的迴文串還一點都沒有匹配,這個時候,就只能老老實實地一個一個匹配了,匹配完成後要更新P的位置和對應的po以及Len[i]。

2.時間複雜度分析

Manacher演算法的時間複雜度分析和Z演算法類似,因為演算法只有遇到還沒有匹配的位置時才進行匹配,已經匹配過的位置不再進行匹配,所以對於T字串中的每一個位置,只進行一次匹配,所以Manacher演算法的總體時間複雜度為O(n),其中n為T字串的長度,由於T的長度事實上是S的兩倍,所以時間複雜度依然是線性的。

下面是演算法的實現,注意,為了避免更新P的時候導致越界,我們在字串T的前增加一個特殊字元,比如說‘$’,所以演算法中字串是從1開始的。、

 var longestPalindrome = function(s) {
  var manaStr = "$#";
    for (var i=0;i<s.length;i++) //首先構造出新的字串
  {
      manaStr += s[i];
      manaStr += '#';
    }
   var rd=[];//用一個輔助陣列來記錄最大的迴文串長度,注意這裡記錄的是新串的長度,原串的長度要減去1
    var pos = 0, mx = 0;
    var start = 0, maxLen = 0;
    for (var i = 1; i < manaStr.length; i++) 
    {
      rd[i] = i < mx ? Math.min(rd[2 * pos - i], mx - i) : 1;
      while (i+rd[i]<manaStr.length && i-rd[i]>0 && manaStr[i + rd[i]] == manaStr[i - rd[i]])//這裡要注意陣列越界的判斷,原始碼沒有注意,release下沒有報錯
          rd[i]++;
      if (i + rd[i] > mx) //如果新計算的最右側端點大於mx,則更新pos和mx
      {
        pos = i;
        mx = i + rd[i];
      }
      if (rd[i] - 1 > maxLen)
    {
        start = (i - rd[i]) / 2;
        maxLen = rd[i] - 1;
      }
    }
    return s.substr(start, maxLen);
 }