1. 程式人生 > 其它 >No.005 Longest Palindromic Substring

No.005 Longest Palindromic Substring

5. Longest Palindromic Substring

  • Total Accepted: 120226
  • Total Submissions: 509522
  • Difficulty: Medium

  Given a string s, find the longest palindromic(迴文) substring in sS. You may assume that the maximum length of s is 1000, and there exists one unique longest palindromic substring.

推薦解法3,直觀,有效,好理解

方法一:暴力搜尋(O(N³))

  這個思路就很簡單了,就是直接求出每一個子串,然後判斷其是否為迴文。我們從子串長度最長開始,依次遞減,如果遇到是迴文的,則直接返回即可。迴圈結束如果沒有迴文,則返回null。

  值得注意的一點是因為題目直接說了肯定存在迴文,並且最大長度的迴文唯一,所以在當字串長度大於1的時候,最大回文長度必定大於1(如果為1的話,則每一個單獨字元都可以作為最長長度的迴文),所以搜尋長度遞減到2就結束了。題目中肯定存在大於2的迴文,所以不會直到最後迴圈結束返回null這一步,所以最後直接寫的返回null無關緊要。

 1 /*
 2  * 方法一:暴力搜尋
 3  */
 4 public String longestPalindrome(String s) {
 5     if (s == null || s.length() == 0 || s.length() == 1) {
 6         return s;
 7     }
 8 
 9     String sub;
10     for (int subLen = s.length(); subLen > 1; subLen--) {
11         for (int startIndex = 0; startIndex <= (s.length() - subLen); startIndex++) {
12             // 列出所有子串,然後判斷子串是否滿足有重複
13             if (startIndex != (s.length() - subLen)) {
14                 sub = s.substring(startIndex, startIndex + subLen);
15             } else {
16                 sub = s.substring(startIndex);
17             }
18             System.out.println(sub);
19             if (isPalindrome(sub)) {
20                 return sub;
21             }
22         }
23     }
24     
25     return null ;
27 }
28 
29 private boolean isPalindrome(String sub) {
30     for(int i = 0 ; i <= sub.length()/2 ; i++){
31         if(sub.charAt(i) != sub.charAt(sub.length()-i-1)){
32             return false ;
33         }
34     }
35     return true ;
36 }

方法二:動態規劃O(N²)

更簡潔的做法,使用動態規劃,這樣可以把時間複雜度降到O(N²),空間複雜度也為O(N²)。做法如下:

首先,寫出動態轉移方程。

Define P[ i, j ] ← true iff the substring Si … Sj is a palindrome, otherwise false.

P[ i, j ] ← ( P[ i+1, j-1 ] and Si = Sj ) ,顯然,如果一個子串是迴文串,並且如果從它的左右兩側分別向外擴充套件的一位也相等,那麼這個子串就可以從左右兩側分別向外擴充套件一位。

其中的base case是

P[ i, i ] ← true P[ i, i+1 ] ← ( Si = Si+1 )

然後,看一個例子。

假設有個字串是adade,現在要找到其中的最長迴文子串。使用上面的動態轉移方程,有如下的過程:

  按照紅箭頭->黃箭頭->藍箭頭->綠箭頭->橙箭頭的順序依次填入矩陣,通過這個矩陣記錄從i到j是否是一個迴文串。

 1 /*
 2  * 方法二:動態規劃的方法
 3  */
 4 public String longestPalindrome2(String s) {
 5     if (s == null || s.length() == 0 || s.length() == 1) {
 6         return s;
 7     }
 8     char [] arr = s.toCharArray() ;
 9     int len = s.length() ;
10     int startIndex = 0 ; 
11     int endIndex = 0 ;
12     boolean [][] dp = new boolean [len][len] ;
13     dp[0][0] = true ;
14     for(int i = 1 ; i < len ; i++){
15         //dp[i][i]置為true
16         dp[i][i] = true ;
17         //dp[i-1][i]判斷true或false
18         if(arr[i-1] != arr[i]){
19             dp[i-1][i] = false ;
20         }else{
21             dp[i-1][i] = true ;
22             startIndex = i-1 ;
23             endIndex = i ;
24         }
25     }
26     //填充其他地方的值
27     for(int l = 2 ; l < len ; l++){
28         for(int i = 0 ; i < len-l ; i++){
29             int j = i+l ; 
30             if(dp[i+1][j-1] && (arr[i] == arr[j])){
31                 dp[i][j] = true ;
32                 if((j-i) > (endIndex - startIndex)){
33                     startIndex = i ; 
34                     endIndex = j ;
35                 }
36             }
37         }
38     }
39     //返回最長迴文字串
40     if(endIndex == (len-1)){
41         return s.substring(startIndex) ;
42     }else{
43         return s.substring(startIndex, endIndex+1) ;
44     }            
45 }

  下面的方法參考自 http://blog.csdn.net/feliciafay/article/details/16984031

方法三:從中間向兩邊展開O(N²)(比動態規劃方法好理解)

  迴文字串顯然有個特徵是沿著中心那個字元軸對稱。比如aha沿著中間的h軸對稱,a沿著中間的a軸對稱。那麼aa呢?沿著中間的空字元''軸對稱。所以對於長度為奇數的迴文字串,它沿著中心字元軸對稱,對於長度為偶數的迴文字串,它沿著中心的空字元軸對稱。對於長度為N的候選字串,我們需要在每一個可能的中心點進行檢測以判斷是否構成迴文字串,這樣的中心點一共有2N-1個(2N-1=N-1 + N)。檢測的具體辦法是,從中心開始向兩端展開,觀察兩端的字元是否相同。程式碼如下:

 1 //從中間向兩邊展開
 2 string expandAroundCenter(string s, int c1, int c2) {
 3   int l = c1, r = c2;
 4   int n = s.length();
 5   while (l >= 0 && r <= n-1 && s[l] == s[r]) {
 6     l--;
 7     r++;
 8   }
 9   return s.substr(l+1, r-l-1);
10 }
11  
12 string longestPalindromeSimple(string s) {
13   int n = s.length();
14   if (n == 0) return "";
15   string longest = s.substr(0, 1);  // a single char itself is a palindrome
16   for (int i = 0; i < n-1; i++) {
17     string p1 = expandAroundCenter(s, i, i); //長度為奇數的候選迴文字串
18     if (p1.length() > longest.length())
19       longest = p1;
20  
21     string p2 = expandAroundCenter(s, i, i+1);//長度為偶數的候選迴文字串
22     if (p2.length() > longest.length())
23       longest = p2;
24   }
25   return longest;
26 }

四、 時間複雜度為O(N)的演算法

  在這裡看到了更更簡潔的做法,可以把時間複雜度降到O(N).具體做法原文說得很清楚,有圖有例,可以仔細讀讀。這裡我只想寫寫,為什麼這個演算法的時間複雜度是O(N)而不是O(N²)。從程式碼中看,for迴圈中還有個while,在2層巢狀的迴圈中,似乎應該是O(N²)的時間複雜度。

 1 // Transform S into T.
 2 // For example, S = "abba", T = "^#a#b#b#a#$".
 3 // ^ and $ signs are sentinels appended to each end to avoid bounds checking
 4 string preProcess(string s) {
 5   int n = s.length();
 6   if (n == 0) return "^$";
 7   string ret = "^";
 8   for (int i = 0; i < n; i++)
 9     ret += "#" + s.substr(i, 1);
10  
11   ret += "#$";
12   return ret;
13 }
14  
15 string longestPalindrome(string s) {
16   string T = preProcess(s);
17   int n = T.length();
18   int *P = new int[n];
19   int C = 0, R = 0;
20   for (int i = 1; i < n-1; i++) {
21     int i_mirror = 2*C-i; // equals to i' = C - (i-C)
22     
23     P[i] = (R > i) ? min(R-i, P[i_mirror]) : 0;
24     
25     // Attempt to expand palindrome centered at i
26     while (T[i + 1 + P[i]] == T[i - 1 - P[i]])
27       P[i]++;
28  
29     // If palindrome centered at i expand past R,
30     // adjust center based on expanded palindrome.
31     if (i + P[i] > R) {
32       C = i;
33       R = i + P[i];
34     }
35   }
36  
37   // Find the maximum element in P.
38   int maxLen = 0;
39   int centerIndex = 0;
40   for (int i = 1; i < n-1; i++) {
41     if (P[i] > maxLen) {
42       maxLen = P[i];
43       centerIndex = i;
44     }
45   }
46   delete[] P;
47   
48   return s.substr((centerIndex - 1 - maxLen)/2, maxLen);
49 }

  時間複雜度為什麼是O(N)而不是O(N²)呢?

   假設真的是O(N²),那麼在每次外層的for迴圈進行的時候(一共n步),對於for的每一步,內層的while迴圈要進行O(N)次。而這是不可能。因為p[i]和R是有相互影響的。while要麼就只走一步,就到了退出條件了。要麼就走很多很步。如果while走了很多步,多到一定程度,會更新R的值,使得R的值增大。而一旦R變大了,下一次進行for迴圈的時候,while條件直接就退出了。