目錄
題目描述
給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。
輸入/輸出示例
輸入 | babad |
輸出 | bab |
解釋 | 輸入的字符串中最長的迴文子串爲bab。注意aba也是一個正確答案。 |
解決方案
解決的方法大概分爲以下三種:
暴力法
很明顯,暴力法將選出所有子字符串可能的開始和結束位置,並檢驗它是不是迴文。在利用Python分片的優勢下,時間複雜度爲 。空間複雜度爲 。
需要注意的是,在Python語言優勢下(分片的使用)暴力法的時間複雜度並不高,但是在不支持分片功能的語言裏(例如Go,雖然支持切片,但是跟Python分片還是有一定區別的),暴力法的時間複雜度會達到驚人的 。
中心擴散法
暴力法雖然邏輯簡單清晰,但是會帶來太多的重複計算。我們通過觀察發現,單個字符肯定是迴文字符串。如果單個字符的左右兩邊擁有相鄰的字符,且左右兩邊相鄰的字符相等,那麼這三個字符所組成的字符串一定就是迴文字符串。按照這個邏輯依此類推,就可以找到中心字符串不斷向兩邊擴散的最長迴文串。我們通過循環可以將每個字符視爲一個“中心字符”,對每一箇中心字符向兩邊擴散,找到最長的記錄,就是我們要的結果。
但是會有一種情況在上面描述的邏輯中沒有考慮到:如果是長度爲偶數的迴文字符串,例如abba,使用上面以單個字符作爲中心向兩邊擴散的邏輯無法找到最終結果“abba”,而是會得到一個錯誤的結果“a”。因此我們還要考慮對長度爲偶數字符串的場景:我們選取兩個相鄰的字符串作爲“中心”。如果這兩個字符相等,那麼就依次向兩邊擴散,如果每次擴散的最外層左右兩邊字符相等,那麼這些字符所構成的字符串就是我們要找的迴文字符串。依次遍歷完全部字符串後找到的長度最大的迴文子串就是我們想要的結果。
綜上所述,要找到一個字符串中的最大回文子串,我們不僅要以單個字符向兩邊擴散求解,還要對兩個相同且相鄰的字符向兩邊擴散求解。最終對兩個方法求解的結果進行比較,找到長度最大的字符串,就是我們想要的結果,即最長迴文子串。
複雜度分析:時間複雜度爲。空間複雜度爲 。
Manacher算法
Manacher算法是對中心擴散法的一種優化。因爲中心擴散法要對子串長度的奇偶分別進行計算,消耗了大量的時間。如果我們對奇偶轉換成統一的模式進行計算,就會減少大量的時間消耗。因此Manacher算法的核心是,將原字符串(無論奇偶)構造成一個長度完全是奇數的字符串:構造後無論是長度,還是內部字符和字符之間的距離都變成了奇數。這種構造新字符串的方式就是將原字符串的首尾和每個字符之間都插入一個特殊的標誌字符。例如假設原字符串爲“abba”,通過插入特殊字符的方式將字符串構造爲“#a#b#b#a”。需要注意的是,該特殊字符不能存在於原始字符串中。我們對構造後的字符串利用中心擴散法的思想求出最長迴文子串。
代碼
暴力法
class Solution:
def longestPalindrome(self, s: str) -> str:
if s == s[::-1]:
return s
max_sub_palindrome_string = s[0]
for i in range(len(s)-1):
for j in range(i+1, len(s)):
if j - i + 1 > len(max_sub_palindrome_string) and \
s[i:j+1] == s[i:j+1][::-1]:
max_sub_palindrome_string = s[i:j+1]
return max_sub_palindrome_string
中心擴散法
class Solution:
def longestPalindrome(self, s: str) -> str:
if s == s[::-1]:
return s
max_sub_palindrome_string = s[0]
for index in range(len(s)):
palindrome_odd = Solution.get_palindrome(index, index, s)
palindrome_even = Solution.get_palindrome(index, index+1, s)
max_sub_palindrome_string = max(
palindrome_odd,
palindrome_even,
max_sub_palindrome_string,
key=len,
)
return max_sub_palindrome_string
@staticmethod
def get_palindrome(left, right, s) -> str:
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return s[left+1:right]
Manacher算法
class Solution:
def longestPalindrome(self, s: str) -> str:
return self.manacher(s)
@staticmethod
def manacher(s: str) -> str:
if len(s) < 2:
return s
# 將一個可能是偶數長/奇數長的字符串,首位以及每個字符間添加#
manacher_length = len(s)*2+1
manacher_str = "#".join(s).center(manacher_length, "#")
max_palindrome = ""
for i in range(manacher_length):
left = i - 1
right = i + 1
while left >= 0 and right < manacher_length and \
manacher_str[left] == manacher_str[right]:
left -= 1
right += 1
if right - left + 1 > len(max_palindrome):
max_palindrome = manacher_str[left+1:right]
return max_palindrome.replace("#", "")
代碼走讀
暴力法
class Solution: def longestPalindrome(self, s: str) -> str: # 如果s本身迴文,或s爲空字符,則返回s if s == s[::-1]: return s # 因爲單字符本身迴文,因此初始化最長子迴文串爲首字符 max_sub_palindrome_string = s[0] # 設置兩個遊標變量,i從頭到尾-1依次遍歷,將每個字符作爲子串的首字符 # j 作爲子串的尾字符, 依次檢查s[i:j+1]是否爲最長迴文串 for i in range(len(s)-1): for j in range(i+1, len(s)): if j - i + 1 > len(max_sub_palindrome_string) and \ s[i:j+1] == s[i:j+1][::-1]: max_sub_palindrome_string = s[i:j+1] return max_sub_palindrome_string # 自測用例 if __name__ == '__main__': s = Solution() result = s.longestPalindrome("hada") print(result)
中心擴散法
class Solution: def longestPalindrome(self, s: str) -> str: if s == s[::-1]: return s max_sub_palindrome_string = s[0] for index in range(len(s)): # 分別以奇數子串和偶數子串爲中心向兩邊擴散,找出最長子串 palindrome_odd = Solution.get_palindrome(index, index, s) palindrome_even = Solution.get_palindrome(index, index+1, s) max_sub_palindrome_string = max( palindrome_odd, palindrome_even, max_sub_palindrome_string, key=len, ) return max_sub_palindrome_string # 以s[left:right+1]爲中心向兩邊擴散,尋找最長迴文子串 @staticmethod def get_palindrome(left, right, s) -> str: while left >= 0 and right < len(s) and s[left] == s[right]: left -= 1 right += 1 return s[left+1:right] # 自測用例 if __name__ == '__main__': s = Solution() result = s.longestPalindrome("babad") print(result)
Manacher算法
class Solution: def longestPalindrome(self, s: str) -> str: return self.manacher(s) @staticmethod def manacher(s: str) -> str: # 如果s是單字符的字符串,那麼直接返回 if len(s) < 2: return s # 將一個可能是偶數長/奇數長的字符串,首位以及每個字符間添加#,確保添加後變成了長度爲奇數的字符串 manacher_length = len(s)*2+1 manacher_str = "#".join(s).center(manacher_length, "#") # 記錄最長子迴文串 max_palindrome = "" for i in range(manacher_length): left = i - 1 right = i + 1 while left >= 0 and right < manacher_length and \ manacher_str[left] == manacher_str[right]: left -= 1 right += 1 # 將左右指針回退一位,得到本次遍歷的迴文串長度:(right - 1) - (left + 1) - 1 = right - left - 1 if right - left - 1 > len(max_palindrome): max_palindrome = manacher_str[left+1:right] # 由於max_palindrom是包含特殊字符#的迴文子串,因此需要將#字符刪除後返回 return max_palindrome.replace("#", "") # 自測用例 if __name__ == '__main__': s = Solution() result = s.longestPalindrome("babad") print(result)