最長迴文子串(Java版本)

目錄

1、題目

2、示例

3、解決方案

3.0 暴力破解

3.1 動態規劃

3.2 中心擴展算法

3.3 Manacher 算法

4.參考


1、題目

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

2、示例

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

示例 2:
輸入: "cbbd"
輸出: "bb"

3、解決方案

3.0 暴力破解

這應該是在沒有其他解法的時候,你應該想到的:暴力求解,列舉所有的子串,判斷是否爲迴文串,保存最長的迴文串。

    public static String longestPalindrome(String s) {
        String ans = "";
        int max = 0;
        int len = s.length();
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j <= len; j++) {
                String tmp = s.substring(i, j);
                if (isPalindromic(tmp) && tmp.length() > max) {
                    ans = tmp;
                    max = j - i;
                }
            }
        }
        return ans;
    }

    private static boolean isPalindromic(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;
    }

時間複雜度:兩層 for 循環 O(n²),for 循環裏邊判斷是否爲迴文 O(n),所以時間複雜度爲 O(n³)。

空間複雜度:O(1),常數個變量。

3.1 動態規劃

暴力解法時間複雜度太高,我們可以考慮,去掉一些暴力解法中重複的判斷。

首先定義:字符串 s 從下標 i 到下標 j 的字串爲 P(i, j),若 s 從下標 i 到下標 j 的字串爲迴文串,則 P(i, j) = true,否則 P(i, j) = false。如下圖所示:

則 P(i, j) = (P(i + 1, j - 1) && s[i] == s[j])。

所以如果我們想知道 P(i,j)的情況,不需要調用判斷迴文串的函數了,只需要知道 P(i+1,j−1)的情況就可以了,這樣時間複雜度就少了 O(n)。因此我們可以用動態規劃的方法,空間換時間,把已經求出的 P(i,j)存儲起來。

image.png

如果 s[i+1, j-1] 是迴文串,那麼只要 s[i] == s[j],就可以確定 s[i, j] 也是迴文串了。

注意:

求 長度爲 1 和長度爲 2 的 P(i, j) 時不能用上邊的公式,因爲我們代入公式後會遇到 P[i][j] 中 i > j 的情況,比如求P[1][2] 的話,我們需要知道 P[1+1][2-1]=P[2][1] ,而 P[2][1] 代表着 S[2, 1] 是不是迴文串,這顯然是不對的,所以我們需要單獨判斷。

所以我們先初始化長度是 1 的迴文串的 P [i , j],這樣利用上邊提出的公式 P(i,j)=(P(i+1,j-1) && S[i]==S[j]),然後兩邊向外各擴充一個字符,長度爲 3 的,爲 5 的,所有奇數長度的就都求出來了。同理,初始化長度是 2 的迴文串 P[i,i+1],利用公式,長度爲 4 的,6 的所有偶數長度的就都求出來了。

    public static String longestPalindrome(String s) {
        int sLen = s.length();
        int maxLen = 0;
        String ans = "";
        boolean[][] P = new boolean[sLen][sLen];
        // 遍歷所有長度
        for (int len = 1; len <= sLen; len++) {
            for (int start = 0; start < sLen; start++) {
                int end = start + len - 1;
                // 下標越界,結束循環
                if (end >=sLen) {
                    break;
                }
                P[start][end] = (len == 1 || len == 2 || P[start + 1][end - 1]) && s.charAt(start) == s.charAt(end);
                if (P[start][end] && len > maxLen) {
                    maxLen = len;
                    ans = s.substring(start, end + 1);
                }
            }
        }
        return ans;
    }

時間複雜度:兩層循環 O(n²)。

空間複雜度:用二維數組 PP保存每個子串的情況 O(n²)。

下面分析空間使用情況:(以”babad“爲例)

當我們求長度爲 5  的子串的情況時,其實只用到了 4 長度的情況,而長度爲 1 和 2  和 3 的子串情況其實已經不需要了。

但是由於我們並不是用 P 數組的下標進行的循環,暫時沒有想到優化的方法。

那麼我們換種思路,公式不變:

其實從遞推公式中我們可以看到,我們首先知道了 i +1 纔會知道 i ,所以我們只需要倒着遍歷就行了。

    public static String longestPalindrome(String s) {
        int sLen = s.length();
        String ans = "";
        boolean[][] P = new boolean[sLen][sLen];

        for (int i = sLen - 1; i >= 0; i--) {
            for (int j = i; j < sLen; j++) {
                P[i][j] = (s.charAt(i) == s.charAt(j)) && (j - i < 2 || P[i + 1][j - 1]);
                if (P[i][j] && j - i + 1 > ans.length()) {
                    ans = s.substring(i, j + 1);
                }
            }
        }
        return ans;
    }

時間複雜度和空間複雜和之前都沒有變化,我們來看看可不可以優化空間複雜度。

當求第 i 行的時候我們只需要第 i+1 行的信息,並且 j 的話需要 j−1 的信息,所以和之前一樣 j 也需要倒序。

    public static String longestPalindrome(String s) {
        int sLen = s.length();
        String ans = "";
        boolean[] P = new boolean[sLen];

        for (int i = sLen - 1; i >= 0; i--) {
            for (int j = sLen - 1; j >= i; j--) {
                P[j] = s.charAt(i) == s.charAt(j) && (j - i < 2 || P[j - 1]);
                if (P[j] && j - i + 1 > ans.length()) {
                    ans = s.substring(i, j + 1);
                }
            }
        }

        return ans;
    }

時間複雜度:不變 O(n²)。

空間複雜度:降爲 O(n )。

3.2 中心擴展算法

我們知道迴文串一定是對稱的,所以我們可以每次循環選擇一箇中心,進行左右擴展,判斷左右字符是否相等即可。

image.png

由於存在奇數的字符串和偶數的字符串,所以我們需要從一個字符開始擴展,或者從兩個字符之間開始擴展,所以總共有

n + (n-1)箇中心。

    public static String longestPalindrome(String s) {
        if (s == null || 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) {
                start = i - (len-1) / 2;
                end = i + len / 2;
            }
        }

        return s.substring(start, end + 1);
    }

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

3.3 Manacher 算法

https://www.cnblogs.com/bitzhuwei/p/Longest-Palindromic-Substring-Part-II.html

4.參考

https://leetcode-cn.com/problems/longest-palindromic-substring/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-bao-gu/

https://www.cnblogs.com/bitzhuwei/p/Longest-Palindromic-Substring-Part-II.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章