[譯]最長回文子串(Longest Palindromic Substring) Part I
英文原文鏈接在(http://leetcode.com/2011/11/longest-palindromic-substring-part-i.html)
問題:給定字符串S,求S中的最長回文子串。
這個有趣的問題常常在面試中出現。為什么呢?因為解決辦法有很多種。單單我知道的就有5種。你能解決這個問題嗎?來Online Judge試試看吧!
提示
首先你要知道回文是什么。回文就是從左右兩邊讀都一樣的字符串。例如”aba”是回文,”abc”不是回文。
一個常見的錯誤
有人很快會想到這樣一個方法。這個方法有缺陷,不過很容易修正。
翻轉S成為S’。查找S和S’最長公共子串,就是S的最長回文子串。 |
看起來有道理的樣子。用實例檢驗下。
例如S=”caba”,S’=”abac”。
S和S’的最長公共子串是”aba”,確實是S的最長回文子串。
再看個例子。
S=”abacdfgdcaba”,S’=”abacdgfdcaba”。
S和S’的最長公共子串是”abacd”,不過很明顯這不是回文。
暴力窮舉法O(N3)
最簡單的就是暴力窮舉(Brute Force)對每個start和end位置的子串進行檢測,判斷其是否回文。顯然有C(N,2)(組合)個子串。檢測每個子串都需要O(N)的時間,所以此方法的時間復雜度為O(N3)。
動態規划法O(N2)時間O(N2)空間
我們可以用動態規划(Dynamic Programming即DP)法對暴力窮舉法進行改進。記住,訣竅就是避免重復計算(即重復檢測同一子串)。考慮這個例子”ababa”。如果我們已經檢測過”bab”是回文,那么只需判斷一下最左右的兩個字符(即兩個a)是否相同即可判定”ababa”是否回文了。
總結起來就是:
定義二維數組P[i,j]用以表示Si…Sj是回文(true)或不是回文(false)
那么可知P[i,j] = (P[i + 1, j - 1] && Si ==Sj)
初始條件是:P[i, i]=true,P[i, i + 1] = (Si == Si+1)
這個DP法的思路就是,首先可以知道單個字符和兩個相鄰字符是否回文,然后檢測連續三個字符是否回文,然后四個。。。
此算法時間復雜度O(N2),空間復雜度O(N2)。偽代碼如下。
1 string longestPalindromeDP(string s) { 2 int n = s.length(); 3 int longestBegin = 0; 4 int maxLen = 1; 5 bool table[1000][1000] = {false}; 6 for (int i = 0; i < n; i++) { 7 table[i][i] = true; 8 } 9 for (int i = 0; i < n-1; i++) { 10 if (s[i] == s[i+1]) { 11 table[i][i+1] = true; 12 longestBegin = i; 13 maxLen = 2; 14 } 15 } 16 for (int len = 3; len <= n; len++) { 17 for (int i = 0; i < n-len+1; i++) { 18 int j = i+len-1; 19 if (s[i] == s[j] && table[i+1][j-1]) { 20 table[i][j] = true; 21 longestBegin = i; 22 maxLen = len; 23 } 24 } 25 } 26 return s.substr(longestBegin, maxLen); 27 }
提問:空間復雜度還能再改進嗎?
更簡單的算法O(N2)時間O(1)空間
下面介紹一個O(N2)時間O(1)空間的算法。
回文的特點,就是中心對稱。對於有N個字符的字符串S,只有2N-1個中心。
為何是2N-1?因為兩個字符之間的空檔也可以是一個中心。例如”abba”的兩個b中間就是一個中心。
圍繞一個中心檢測回文需要O(N)時間,所以總的時間復雜度是O(N2)。
1 string expandAroundCenter(string s, int c1, int c2) { 2 int l = c1, r = c2; 3 int n = s.length(); 4 while (l >= 0 && r <= n-1 && s[l] == s[r]) { 5 l--; 6 r++; 7 } 8 return s.substr(l+1, r-l-1); 9 } 10 11 string longestPalindromeSimple(string s) { 12 int n = s.length(); 13 if (n == 0) return ""; 14 string longest = s.substr(0, 1); // a single char itself is a palindrome 15 for (int i = 0; i < n-1; i++) { 16 string p1 = expandAroundCenter(s, i, i); 17 if (p1.length() > longest.length()) 18 longest = p1; 19 20 string p2 = expandAroundCenter(s, i, i+1); 21 if (p2.length() > longest.length()) 22 longest = p2; 23 } 24 return longest; 25 }
PS:“中心檢測法”是我胡謅的名字。
提問O(N)
是否存在O(N)時間的算法?當然有!不過理解起來有點費勁,我們下回分解。