5. Longest Palindromic Substring

題目描述

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

輸入與輸出

class Solution {
public:
    string longestPalindrome(string s) {
        
    }
};

樣例

  1. Input:"babad",Output:"bab",Note:"aba" is also a valid answer。
  2. Input:"cbbd",Output:"bb"。

題解與分析

解法一

鑒于 s 的最大長度為1000,可以考慮暴力解法,即枚舉每一個可能的中間位置,分別向兩側擴展,得到以該位置為中心的最長回文字串。枚舉過程中記錄最長回文子串的信息,最后返回子串即可。

該解法中需要注意的一點:回文子串的長度的奇偶性不確定,因此每個位置需要兩次擴展過程。

C++ 代碼如下:

class Solution {
public:
    string longestPalindrome(string s) {
        int size = s.size();
        int start = -1, length = 0; // 維護最長回文子串相關信息
        // 枚舉可能的中心位置
        for (int i = 0; i < size; ++i) {
            // 回文子串長度為奇數的情況
            int left = i - 1, right = i + 1;
            while (left >= 0 && right < size && s[left] == s[right])
                --left, ++right;
            if (right - left - 1 > length)
                start = left + 1, length = right - left - 1;
            // 回文子串長度為偶數的情況
            left = i, right = i + 1;
            while (left >= 0 && right < size && s[left] == s[right])
                --left, ++right;
            if (right - left - 1 > length)
                start = left + 1, length = right - left - 1;
        }
        return s.substr(start, length);
    }
};

該解法的時間復雜度為 O(n^2)

解法二

3. Longest Substring Without Repeating Characters 類似,上述解法的時間復雜度高達 O(n^2) 的原因是沒有利用之前枚舉過程中得到的信息,導致大量重復的判斷。

下面介紹 Manacher 算法,該算法的時間復雜度為 O(n),缺點是只能處理長度為奇數的回文子串(可以預處理字符串使該算法也適用于偶數長度子串)。

預處理過程:在原字符串兩端與字符之間插入原字符串中未出現過的字符(本文中使用 '#'),例如 abacabba 處理后為 #a#b#a#c#a#b#b#a#。這樣所有的回文子串均為奇數長度,例如 #a#b#a##a#b#b#a#

設處理過的字符串為 Ma,建立一個同等長度的 int 數組 MpMp[i]用來記錄以位置i為中心的最長回文子串的右半部分的長度(不包括位置i本身),例如#a#b#b#a#的右半部分為b#a#,相應地,Mp[i] = 4。通過上例還可以得到Mp數組的另一個意義,它記錄了相應回文子串在原字符串對應的長度。

除此之外,建立變量Mx用來記錄當前所有擴展過程中涉及到的最右側字符的位置,建立變量Id記錄擴展到該位置時的中心位置。

下面分情況討論該算法如何利用之前枚舉過程中的信息,也是該算法的核心,對應代碼Mp[i] = Mx > i ? min(Mp[2 * Id - i], Mx - i) : 0;

  • 首先判斷Mxi的位置關系:
  • Mx > i:說明之前某個回文子串曾經擴展到該位置右側,則該位置兩側字符中有一部分信息之前獲取過。這部分信息保存在Mp[2 * id - i]中,其中2 * id - ii關于Id的對稱位置。進一步討論:
  • Mp[2 * Id - i] <= Mx - i:即以2 * id - i為中心的最長回文子串在以id為中心的最長回文子串內部,如下圖所示:

    由于橙色部分是回文子串,那么兩個紅色部分是鏡像關系,如果左側紅色部分是最長回文子串,那么右側紅色部分也是對應位置的最長回文子串。即Mp[i] = Mp[2 * Id - i]
  • Mp[2 * Id - i] > Mx - i:即以2 * id - i為中心的最長回文子串的左側超出了以id為中心的最長回文子串。如下圖所示:

    與上種情況類似,但是以2 * id - i為中心的最長回文子串中只有紅色部分在以id為中心的最長回文子串內部,因此右側對應的紅色部分也是回文子串。但是在Mx右側的字符尚未拓展,因此需要進一步拓展獲得最大回文子串。初始化Mp[i] = Mx - i
  • Mx <= i:這種情況下i右側的字符尚未拓展過,需要拓展獲得最長回文子串。初始化Mp[i] = 0

C++ 代碼如下:

class Solution {
public:
    string longestPalindrome(string s) {
        int size = s.size() * 2 + 1;
        char *Ma = new char[size];
        int *Mp = new int[size];
        int Mx = -1, Id = -1;
        int start, length = 0;
        Ma[0] = '#';
        for (int i = 0, p = 0; i < s.size(); ++i) {
            Ma[++p] = s[i];
            Ma[++p] = '#';
        }
        for (int i = 0; i < size; ++i) {
            Mp[i] = Mx > i ? min(Mp[2 * Id - i], Mx - i) : 0;
            while (i - Mp[i] - 1 >= 0 && i + Mp[i] + 1 < size && Ma[i - Mp[i] - 1] == Ma[i + Mp[i] + 1])
                ++Mp[i];
            if (i + Mp[i] > Mx)
                Mx = i + Mp[i], Id = i;
            if (Mp[i] > length)
                length = Mp[i], start = (i - Mp[i]) / 2;
        }
        delete[] Ma;
        delete[] Mp;
        return s.substr(start, length);
    }
};

時間復雜度分析:上述代碼中,第一個 for 循環為 O(n),第二個 for 循環外層為 O(n),下面分析內部的 while 循環。由上述算法流程可知,每一次擴展操作都是在Mx右側擴展,即每執行一次++Mp[i];都會導致Mx增加 1,即Mx單調遞增。因為Mx的取值范圍是[0, size),所以 while 循環內部代碼最多執行 O(n) 次。

該解法的時間復雜度為 O(n)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容