Longest Palindromic Substring

  • tags: [palindrome]

Question

  • leetcode: Longest Palindromic Substring | LeetCode OJ
  • lintcode: (200) Longest Palindromic Substring

    1. Given a string S, find the longest palindromic substring in S.
      1. You may assume that the maximum length of S is 1000,
      2. and there exists one unique longest palindromic substring.
        1. Example
      3. Given the string = "abcdzdcab", return "cdzdc".
      4. Challenge
      5. O(n2) time is acceptable. Can you do it in O(n) time.

題解1 - 窮舉搜索(brute force)

最簡單的方案,窮舉所有可能的子串,判斷子串是否為回文,使用一變數記錄最大回文長度,若新的回文超過之前的最大回文長度則更新標記變數並記錄當前回文的起止索引,最後返回最長回文子串。

Python

  1. class Solution:
    2.     # @param {string} s input string
    3.     # @return {string} the longest palindromic substring
    4.     def longestPalindrome(self, s):
    5.         if not s:
    6.             return ""
    7.   8.         n = len(s)
    9.         longest, left, right = 0, 0, 0
    10.         for i in xrange(0, n):
    11.             for j in xrange(i + 1, n + 1):
    12.                 substr = s[i:j]
    13.                 if self.isPalindrome(substr) and len(substr) > longest:
    14.                     longest = len(substr)
    15.                     left, right = i, j
    16.         # construct longest substr
    17.         result = s[left:right]
    18.         return result
    19.   20.     def isPalindrome(self, s):
    21.         if not s:
    22.             return False
    23.         # reverse compare
    24.         return s == s[::-1]

C++

  1. class Solution {
    2. public:
    3.     /**
    4.      * @param s input string
    5.      * @return the longest palindromic substring
    6.      */
    7.     string longestPalindrome(string& s) {
    8.         string result;
    9.         if (s.empty()) return s;
    10.   11.         int n = s.size();
    12.         int longest = 0, left = 0, right = 0;
    13.         for (int i = 0; i < n; ++i) {
    14.             for (int j = i + 1; j <= n; ++j) {
    15.                 string substr = s.substr(i, j - i);
    16.                 if (isPalindrome(substr) && substr.size() > longest) {
    17.                     longest = j - i;
    18.                     left = i;
    19.                     right = j;
    20.                 }
    21.             }
    22.         }
    23.   24.         result = s.substr(left, right - left);
    25.         return result;
    26.     }
    27.   28. private:
    29.     bool isPalindrome(string &s) {
    30.         int n = s.size();
    31.         for (int i = 0; i < n; ++i) {
    32.             if (s[i] != s[n - i - 1]) return false;
    33.         }
    34.         return true;
    35.     }
    36. };

Java

  1. public class Solution {
    2.     /**
    3.      * @param s input string
    4.      * @return the longest palindromic substring
    5.      */
    6.     public String longestPalindrome(String s) {
    7.         String result = new String();
    8.         if (s == null || s.isEmpty()) return result;
    9.   10.         int n = s.length();
    11.         int longest = 0, left = 0, right = 0;
    12.         for (int i = 0; i < n; i++) {
    13.             for (int j = i + 1; j <= n; j++) {
    14.                 String substr = s.substring(i, j);
    15.                 if (isPalindrome(substr) && substr.length() > longest) {
    16.                     longest = substr.length();
    17.                     left = i;
    18.                     right = j;
    19.                 }
    20.             }
    21.         }
    22.   23.         result = s.substring(left, right);
    24.         return result;
    25.     }
    26.   27.     private boolean isPalindrome(String s) {
    28.         if (s == null || s.isEmpty()) return false;
    29.   30.         int n = s.length();
    31.         for (int i = 0; i < n; i++) {
    32.             if (s.charAt(i) != s.charAt(n - i - 1)) return false;
    33.         }
    34.   35.         return true;
    36.     }
    37. }

源碼分析

使用left, right作為子串的起止索引,用於最後構造返回結果,避免中間構造字串以減少開銷。

複雜度分析

窮舉所有的子串,O(C_n^2) = O(n^2), 每次判斷字符串是否為回文,複雜度為 O(n), 故總的時間複雜度為 O(n^3). 故大數據集下可能 TLE. 使用了substr作為臨時子串,空間複雜度為 O(n).

題解2 - 動態規劃(dynamic programming)

要改善效率,可以觀察哪邊有重複而冗餘的計算,例如已知”bab”為回文的情況下,若前後各加一個相同的字元,”cbabc”,當然也是回文。
因此可以使用動態規劃,將先前的結果儲存起來,假設字串s的長度為n,我們創建一個(n\times n)的bool值矩陣P,P[i, j], i<= j表示由[s_i, …, s_j]構成的子串是否為回文。就可以得到一個與子結構關係

P[i, j] = P[i+1, j-1] AND s[i] == s[j]
而基本狀態為

P[i, i] = true

與

P[i, i+1] = (s[i] == s[i+1])
因此可以整理成程式碼如下
1. string longestPalindrome(string s) {
2. int n = s.length();
3. int maxBegin = 0;
4. int maxLen = 1;
5. bool table[1000][1000] = {false};
6. 7. for (int i = 0; i < n; i++) {
8. table[i][i] = true;
9. }
10. 11. for (int i = 0; i < n-1; i++) {
12. if (s[i] == s[i+1]) {
13. table[i][i+1] = true;
14. maxBegin = i;
15. maxLen = 2;
16. }
17. }
18. for (int len = 3; len <= n; len++) {
19. for (int i = 0; i < n-len+1; i++) {
20. int j = i+len-1;
21. if (s[i] == s[j] && table[i+1][j-1]) {
22. table[i][j] = true;
23. maxBegin = i;
24. maxLen = len;
25. }
26. }
27. }
28. return s.substr(maxBegin, maxLen);
29. }

複雜度分析

仍然是兩層迴圈,但每次迴圈內部只有常數次操作,因此時間複雜度是O(n^2),另外空間複雜度是O(n^2)

題解 3 - Manacher’s Algorithm

Reference

  • Longest Palindromic Substring Part I | LeetCode
  • Longest Palindromic Substring Part II | LeetCode