寫在前面:忍不住吐槽幾句今天上海的天氣,次奧,鞋子里都能養魚了...褲子也全濕了,衣服也全濕了,關鍵是這天氣還打空調,只能瑟瑟發抖祈禱不要感冒了....
前后切了一百零幾道leetcode的題(solution同步在github),主要是揀難度系數定為easy的水題做...好吧,這是第一道算法題。不知哪位大神說的,所有的語言都會過時,只有數據結構和算法才是永恆。
今天要重點講的是優雅的Manacher算法
,先來看這道題Longest Palindromic Substring,題目很簡單,給你一個字符串,找到最長的回文子串。啥叫回文串?就是前后看都一樣的串,比如abcba
,abba
,因為題目給的數據量不大(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;
};