1. 程式人生 > 實用技巧 >leetcode-5-最長迴文子串

leetcode-5-最長迴文子串

目錄


本題是leetcode,地址:5. 最長迴文子串

題目

給定一個字串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度為 1000。

示例 1:

輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個有效答案。
示例 2:

輸入: "cbbd"
輸出: "bb"

分析

題目還是很好理解的,思路有大致三種,一種暴力解、一種動態規劃、一種是中心擴散法;

暴力解決分析:根據迴文子串的定義,列舉所有長度大於等於 2的子串,依次判斷它們是否是迴文,在列舉的過程中需要記錄“當前子串的起始位置”和“子串長度”,最後通過substring擷取一下對應的字串就好了。很容發現,這種解法效率很低!時間複雜度:O(N^3),N是字串的長度,列舉字串的左邊界、右邊界,然後繼續驗證子串是否是迴文子串,這三種操作都與 NN 相關;

code-暴力解決

   public boolean check(String s, int left, int right) {
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

   public String longestPalindrome(String s) {
        if (s == null || s.length() < 1) {
            return "";
        }
        int start = 0, maxLenght = 1;
        char[] chars = s.toCharArray();
        for (int left = 0; left < chars.length; left++) {
            for (int right = chars.length - 1; right > left; right--) {
                boolean check = check(s, left, right);
                if (check && right - left + 1 > maxLenght) {
                    start = left;
                    maxLenght = right - left + 1;
                    break;
                }
            }
        }
        return s.substring(start, start + maxLenght);
    }

code-動態規劃

在設計動態規劃時,需要考慮的點如下:

思考狀態
思考狀態轉移方程
思考初始化
思考輸出
思考優化空間

迴文字串的特點:

如果一個字串的頭尾兩個字元都不相等,那麼這個字串一定不是迴文串;
如果一個字串的頭尾兩個字元相等,才有必要繼續判斷下去。
如果裡面的子串是迴文,整體就是迴文串;
如果裡面的子串不是迴文串,整體就不是迴文串。

如果dp[i] [j] 表示子串 s[i..j] 是否為迴文子串,這裡子串 s[i..j] 定義為左閉右閉區間,可以取到 s[i]s[j]

  • 狀態轉換公式:
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]

邊界條件:表示式 [i + 1, j - 1] 不構成區間,即長度嚴格小於 2,即 j - 1 - (i + 1) + 1 < 2 ,即 j - i < 3。怎麼理解?即s[i..j] 的長度等於 2 或者等於 3 的時候,其實只需要判斷一下頭尾兩個字元是否相等就可以直接下結論,而不需要參考之前的結果;

  • 初始化:單個字元一定是迴文串,因此把對角線先初始化為 true,即 dp[i][i] = true
  • 輸出:dp[i][j] = true,記錄子串的長度和起始位置,當然我們是找最長的,需要跟上次記錄的做比較;

時間複雜度:O(N^2)

根據以上資訊就可以寫我們的程式碼了


    public String longestPalindrome(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        int begin = 0, maxLen = 1;
        // 狀態記錄
        boolean[][] dp = new  boolean[len][len];
        // 初始化:單個字元一定是迴文串,因此把對角線先初始化為 `true`,即 `dp[i][i] = true` 
        for (int i = 0; i < len; i++) {
            dp[i][i] = true;
        }
        for (int j = 1; j < len ; j ++) {
            for (int i = 0; i < len; i ++) {
                // 判斷是否相等
                if (s.charAt(i) != s.charAt(j)) {
                    dp[i][j] = false;
                } else {
                    // 邊界條件:即`s[i..j]` 的長度等於 `2` 或者等於 `3`
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        // 狀態轉換公式:dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                // 記錄begin和長度
                if (dp[i][j] && j - i + 1 > maxLen) {
                    begin = i;
                    maxLen = j - i + 1;
                }
            }
        }

        return s.substring(begin, begin + maxLen);
    }

code-中心擴散法

暴力法採用雙指標兩邊夾,驗證是否是迴文子串。如果列舉可能出現的迴文子串的“中心位置”,從“中心位置”嘗試儘可能擴散出去,得到一個迴文串。遍歷每一個索引,以這個索引為中心,利用“迴文串”中心對稱的特點,往兩邊擴散,看最多能擴散多遠。列舉“中心位置”時間複雜度為 O(N),從“中心位置”擴散得到“迴文子串”的時間複雜度為 O(N),時間複雜度可以降到 O(N^2)。

  • 奇數迴文串的“中心”是一個具體的字元,例如:迴文串 "aba" 的中心是字元 "b";
  • 偶數迴文串的“中心”是位於中間的兩個字元的“空隙”,例如:迴文串串 "abba" 的中心是兩個 "b" 中間的那個“空隙”。

    public String longestPalindrome3(String s) {

        int len = s.length();
        if (len < 2) {
            return s;
        }

        int start = 0, maxLength = 1;
        for (int i = 1; i < len; i++) {
            int one = around(s, i, i);
            int two = around(s, i, i + 1);
            int length = Math.max(one, two);
            if (length > maxLength) {
                start = i - (length - 1) / 2;
                maxLength = length;
            }
        }
        return s.substring(start, start + maxLength);
    }

    public int around(String s, int left, int right) {
        while (left > 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        return right - left - 1;
    }

你的鼓勵也是我創作的動力

打賞地址