每日一道 LeetCode (48):最長迴文子串

每天 3 分鐘,走上算法的逆襲之路。

前文合集

每日一道 LeetCode 前文合集

代碼倉庫

GitHub: //github.com/meteor1993/LeetCode

Gitee: //gitee.com/inwsy/LeetCode

題目:最長迴文子串

難度:中等

題目來源://leetcode-cn.com/problems/longest-palindromic-substring/

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

示例 1:

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

示例 2:

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

解題方案一:暴力解法

暴力方案就很適合我這種腦子不夠用的人,首先把字符串中的所有子串取出來,當然哈,取長度大於 1 的子串,不然也沒啥意義。

然後挨個判斷取出來的子串是否迴文串,判斷完了以後取到最大的那個,然後返回出來,結束。

public String longestPalindrome(String s) {
    int len = s.length();
    if (len < 2) {
        return s;
    }
    // 先把字符串轉成數組
    char[] charArray = s.toCharArray();
    // 定義初始位置
    int left = 0;
    // 定義字符串長度
    int length = 1;
    // 獲取所有子串
    for (int i = 0; i < len - 1; i++) {
        for (int j = i + 1; j < len; j++) {
            if (j - i + 1 > length && valid(charArray, i, j)) {
                left = i;
                length = j - i + 1;
            }
        }
    }
    return s.substring(left, left + length);
}

// 判斷是否迴文串
private boolean valid(char[] charArray, int left, int right) {
    while (left < right) {
        if (charArray[left] != charArray[right]) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

這個答案慢肯定是慢,時間複雜度達到了 O(n^3) ,不慢才有鬼了。

解題方案二:中心擴散

上面的方案是從兩邊開始往中間夾,中心擴散的思想是從中間開始往兩邊進行擴散。

我在答案中找到了一個非常形象的圖解,和大家分享一下:

解釋一下這張圖,現在假設有一個字符串 「acdbbdaa」 ,從第一個 b 位置開始找最長迴文串。

  • 首先往左尋找與當期位置相同的字符,直到遇到不相等為止。
  • 然後往右尋找與當期位置相同的字符,直到遇到不相等為止。
  • 最後左右雙向擴散,直到左和右不相等。

這時我們找到了從 b 開始最長的迴文子串,然後在程序中記錄下來。

每一個位置開始都按這個方案去找最長的迴文子串,最後得到的結果就是最長的迴文子串。

public String longestPalindrome_1(String s) {
    if (s == null || s.length() == 0) return "";

    int length = s.length();
    // 定義左右指針
    int left = 0, right = 0;
    // 定義長度
    int len = 1;
    // 定義最大開始位置和最長子串長度
    int maxStart = 0, maxLen = 0;
    for (int i = 0; i < length; i++) {
        left = i - 1;
        right = i + 1;
        // 計算左邊
        while (left >= 0 && s.charAt(left) == s.charAt(i)) {
            len++;
            left--;
        }
        // 計算右邊
        while (right < length && s.charAt(right) == s.charAt(i)) {
            len++;
            right++;
        }
        // 兩邊一起擴散
        while (left >= 0 && right < length && s.charAt(left) == s.charAt(right)) {
            len += 2;
            left--;
            right++;
        }
        // 如果當前長度大於最大長度
        if (len > maxLen) {
            maxLen = len;
            maxStart = left;
        }
        // 下次循環前重置 len
        len = 1;
    }
    return s.substring(maxStart + 1, maxStart + 1 + maxLen);
}

解題方案三:動態規劃

動態規劃我的理解實際上就是一個把已經判斷過的信息緩存起來的一種方案,很像我們在做 Web 開發的時候用到的 redis ,只是在答案中換成了一個矩陣或者說是二維數組。

如果一個字符串 s[l, r] 是一個迴文串,那麼 s[l + 1, r – 1] 一定也是一個迴文串,相當於兩頭各去掉一個字符。

如果這時我們直接通過某種方式直接可以知道 s[l + 1, r – 1] 是一個迴文串,那麼只需要判斷 s[i] 和 s[j] 相等就可以了。

所以就有了下面的代碼:

public String longestPalindrome_2(String s) {
    if (s == null || s.length() == 0) return s;
    int length = s.length();
    int maxStart = 0;  //最長迴文串的起點
    int maxEnd = 0;    //最長迴文串的終點
    int maxLen = 1;  //最長迴文串的長度

    // 定義一個布爾矩陣
    boolean[][] dp = new boolean[length][length];
    for (int r = 1; r < length; r++) {
        for (int l = 0; l < r; l++) {
            if (s.charAt(r) == s.charAt(l) && (r - l <= 2 || dp[l + 1][r - 1] == true)) {
                dp[l][r] = true;
                if (r - l + 1 > maxLen) {
                    maxLen = r - l + 1;
                    maxStart = l;
                    maxEnd = r;
                }
            }
        }
    }
    return s.substring(maxStart, maxEnd + 1);
}

可以看到耗時也是蠻多的。

答案中還給出了一種叫做 Manacher 算法的方案,原諒我比較菜,屬實是沒看懂,就不拿出來獻醜了。

Tags: