1. 程式人生 > 實用技巧 >[LeetCode] 5. 最長迴文子串

[LeetCode] 5. 最長迴文子串

目錄




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

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

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

分析

  1. 列舉所有子串,判斷是否是迴文
  2. 中心拓展演算法,有2n-1箇中心,可穿插符號簡化
  3. 求最長公共子串,使用動態規劃,注意檢查子串索引是否與反向子串原始索引相同
  4. 如果i,j為迴文,並且S(i-1)等於S(j+1),則i-1,j+1也是迴文子串,使用動態規劃
  5. Manacher演算法時間複雜度為O(n)

解法

解法一

  • 列舉所有子串

  • 時間複雜度O(\(n^3\))

  • 空間複雜度O(1)

class Solution {
    public String longestPalindrome(String s) {
        String ans = "";
        int max = 0, len = s.length();
        // 遍歷起點
        for(int i = 0; i < len; i++){
            // 遍歷終點,排除剩餘長度小於max的情況
            for(int j = i + 1; j < len + 1 && len - i > max; j++){
                // 擷取子串
                String test = s.substring(i, j);
                // 判斷是否為迴文,且長度大於max
                if(isPalindrome(test) && test.length() > max){
                    ans = test;
                    max = test.length();
                }
            }
        }
        return ans;
    }
    // 判斷是否為迴文
    private boolean isPalindrome(String s){
        int len = s.length();
        for(int i = 0; i < len/2; i++){
            if(s.charAt(i) != s.charAt(len - i -1))
                return false;
        }
        return true;
    }
}

//class Test{
//    public static void main(String[] args) {
//        Solution s = new Solution();
//        String res = s.longestPalindrome("babad");
//        System.out.println(res);
//    }
//}

解法二

  • 中心擴充套件演算法

  • 穿插#字元,迴文子串中心一側的字元數即為該子串長度,如#a|#|a#或#a#|b|#a#

  • 時間複雜度O(\(n^2\))

  • 空間複雜度O(1)

class Solution {
    public String longestPalindrome(String s) {
        // 穿插#字元
        String str = "#";
        for(int i = 0; i < s.length(); i++){
            str += s.charAt(i) + "#";
        }
        int center = 0, maxLen = 0;
        // 遍歷擴充套件中心
        for(int i = 0; i < str.length(); i++){
            int len = expandAroundCenter(str, i);
            if(len > maxLen){
                center = i;
                maxLen = len;
            }
        }
        return s.substring((center - maxLen) / 2, (center + maxLen + 1) / 2);
    }
    private int expandAroundCenter(String str, int i){
        int r = 0;
        while(i -r >= 0 && i + r <= str.length() - 1 &&
                str.charAt(i - r) == str.charAt(i + r)){
            r++;
        }
        // r多加了一次
        return r - 1;
    }
}
  • 不穿插#字元

  • 時間複雜度O(\(n^2\))

  • 空間複雜度O(1)

class Solution {
    public String longestPalindrome(String s) {
        if(s.length() < 1) return "";
        int start = 0, end = 0;
        for(int i = 0; i < s.length(); i++){
            int len1 = expandAroundCenter(s, i, i);// 一個字元為中心
            int len2 = expandAroundCenter(s, i , i + 1);// 兩個字元為中心
            int len = Math.max(len1, len2);
            if(len > end - start + 1){
                start = i - (len - 1) / 2;
                end = i + len / 2;
            }
        }
        return s.substring(start, end + 1);

    }
    private int expandAroundCenter(String s, int left, int right){
        while(left >= 0 && right <= s.length() - 1 && 
              s.charAt(left) == s.charAt(right)){
            left--;
            right++;
        }
        return right - left - 1;// (right - 1) - (left + 1) + 1, 回退到上一位置計算
    }
}

解法三

  • 動態規劃求最長公共子串

  • 二維陣列,若字元相等,arr[i][j]=arr[i-1][j-1]+1

    i=0或j=0時,字元相等為1

    可使用一維陣列減小空間複雜度,j需從下向上更新,因為需要根據上面的元素計算下面的元素

    注意類似abc123cba情況,子串索引和反向子串原始索引不同

  • 時間複雜度O(\(n^2\))

  • 空間複雜度O(n​)(一維陣列)

class Solution {
    public String longestPalindrome(String s) {
        if(s.equals("")) return "";
        // 原始字串
        String origin = s;
        // 反轉字串
        String reverse = new StringBuffer(s).reverse().toString();
        int length = s.length();
        int[] arr = new int[length];
        int maxLen = 0;
        int maxStart = 0;
        for(int i = 0; i < length; i++){
            // j從下向上更新
            for(int j = length - 1; j >= 0; j--){
                if(origin.charAt(i) == reverse.charAt(j)){
                    // i為0或j為0時若字元相等元素為1
                    if(i == 0 || j == 0){
                        arr[j] = 1;
                    }else{
                        // 若字元相等元素為上一元素加1
                        arr[j] = arr[j-1] + 1;
                    }
                }else{
                    // 若字元不相等元素為0
                    arr[j] = 0;
                }
                if(arr[j] > maxLen){
                    int beforeRev = length - 1 -j;
                    // 排除位置不對應情況
                    if(beforeRev + arr[j] - 1== i){
                        maxLen = arr[j];
                        maxStart = beforeRev;
                    }
                }
            }
        }
        return s.substring(maxStart, maxStart + maxLen);
    }
}

解法四

  • 動態規劃

  • 根據(i+1,j-1)確定(i,j)是否為迴文子串,不再呼叫判斷函式,空間換時間

    狀態轉移方程:dp[i][j] = s[i]==s[j] && dp[i+1][j-1]

    dp[i+1][j-1]為左下單元格,使用一維陣列需從下至上,從右至左遍歷i和j((5,5)->(4,5)-(4,4)->(3,5)->(3,4)->(3,3)...)

    i+1>j-1時,即j-i<2,長度為1、2時需單獨判斷(可結合圖看出)

  • 時間複雜度O(\(n^2\))

  • 空間複雜度O(n​)(一維陣列)

class Solution {
    public String longestPalindrome(String s) {
        if(s.equals("")) return "";
        int length = s.length();
        boolean[] dp = new boolean[length];
        // 最長迴文子串長度及起始索引
        int maxLen = 1;
        int maxStart = 0;
        int maxEnd = 0;
        // 從下至上,從右至左遍歷i和j
        for(int i = length - 1; i >= 0; i--){
            for(int j = length - 1; j >= i; j--){
                // 長度為1、2時單獨判斷
                dp[j] = s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[j - 1]);
                // 若長度大於maxLen,更新子串起始索引
                if(dp[j] && j - i + 1 > maxLen){
                    maxLen = j - i + 1;
                    maxStart = i;
                    maxEnd = j;
                }
            }
        }
        return s.substring(maxStart, maxEnd + 1);
    }
}