求最長回文子串 - leetcode 5. Longest Palindromic Substring


寫在前面:忍不住吐槽幾句今天上海的天氣,次奧,鞋子里都能養魚了...褲子也全濕了,衣服也全濕了,關鍵是這天氣還打空調,只能瑟瑟發抖祈禱不要感冒了....

前后切了一百零幾道leetcode的題(solution同步在github),主要是揀難度系數定為easy的水題做...好吧,這是第一道算法題。不知哪位大神說的,所有的語言都會過時,只有數據結構和算法才是永恆。

今天要重點講的是優雅的Manacher算法,先來看這道題Longest Palindromic Substring,題目很簡單,給你一個字符串,找到最長的回文子串。啥叫回文串?就是前后看都一樣的串,比如abcbaabba,因為題目給的數據量不大(1000),所以可以枚舉字符串的每個位置當做回文對稱點,回文對稱點是我給它的一個概念,比如abcba的回文對稱點就是idx=2也就是c的位置。But!並不是每個回文串都有對稱點,比如abba,只有對稱軸,它就沒有點!怎么辦?機智的coder想出了一個簡單的用空間換取代碼實現復雜度的方法,這也是Manacher算法的第一步:

abcba -> #a#b#c#b#a#
abba  -> #a#b#b#a#

這么一來,每個回文串就都有回文對稱點了(可能是字母,也可能是#)。之后我們就能枚舉對稱點,然后向兩邊擴散開去,比較字符是否一樣。為了不用判斷是否已經到了邊界,我們最初在字符串的開頭再加個字符*,只要該字符和#以及字符串里其他字符都不一致即可。這樣是可以AC的,雖然復雜度達到了O(n^2)。接下去我們介紹復雜度為O(n)的Manacher算法。

我們試着以字符串babcbade舉例,首先把字符串像上面一樣變形:

babcbade -> *#b#a#b#c#b#a#d#e#

然后我們設置一個dp數組,dp[i]表示以變形后第i個元素為對稱點的最長回文子串的半徑,同樣以上面的字符串舉例,可以得到dp數組:

*#b#a#b#c#b#a#d#e#
112121216121212121

我們可以很容易地發現,要求的最長回文子串的長度即dp數組最大值減去1。於是如何快速地求得該數組成為關鍵。假設我們已經得到了dp[6]的值,dp[10]的初始值也不難確定,因為它們兩個元素根據idx=8對稱(#a#b#c#b#a#),所以可以不用從1開始向兩邊擴散了。

我們用maxn維護當前存在的回文子串能達到最右的位置+1(maxn位置不可達到),用idx維護當前能到達最右+1的回文子串的回文中心點位置,實現該dp數組求值的核心代碼如下:

for (var i = 1, len = str.length; i < len; i++) {
  if (maxn > i) dp[i] = Math.min(dp[2 * idx - i], maxn - i);
  else dp[i] = 1;

  while (str[i - dp[i]] === str[i + dp[i]]) dp[i]++;

  if (dp[i] + i > maxn)
    maxn = dp[i] + i, idx = i;
}

完整的AC代碼:

// return the Longest Palindromic Substring of s
function Manacher(s) {
  var str = '*#'
    , dp = []
    , maxn = 0
    , idx = 0;

  for (var i = 0, len = s.length; i < len; i++)
    str += s[i] + '#';

  for (var i = 1, len = str.length; i < len; i++) {
    if (maxn > i) dp[i] = Math.min(dp[2 * idx - i], maxn - i);
    else dp[i] = 1;

    while (str[i - dp[i]] === str[i + dp[i]]) dp[i]++;

    if (dp[i] + i > maxn)
      maxn = dp[i] + i, idx = i;
  }

  var ans = 0
    , pos;

  for (var i = 1; i < len; i++) {
    if (dp[i] > ans)
      ans = dp[i], pos = i;
  }

  var f = str[pos] === '#'
    , tmp = f ? '' : str[pos]
    , startPos = f ? pos + 1 : pos + 2
    , endPos = f ? dp[pos] - 3 + startPos : dp[pos] - 4 + startPos;

  for (var i = startPos; i <= endPos; i += 2) 
    tmp = str[i] + tmp + str[i];

  return tmp;
}

var longestPalindrome = function(s) {
  var str = Manacher(s);
  return str;
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM