寫在前面
這個題目之前在高中的時候就曾經寫過,現在到了研究生很多東西需要再重新撿起來的呢,在面試中也曾經兩次遇到這樣的題目,現在在今天2020年3月12日10:14:49統一整理一下,預計花費2個小時的時間。
題目描述
給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。
示例 1:
輸入: “babad” 輸出: “bab” 注意: “aba” 也是一個有效答案。 示例 2:
輸入: “cbbd” 輸出: “bb”
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
方法一:最長公共子串
最大的迴文子串,可以替換成最長公共子串,兩個字符串最長的公共的部分的,但是存在反向副本時,策略失效,需要補充條件進行判定,如果反向子串索引和原始索引相同則更新。
花了一個小時學習最長公共子串的算法,花了半個小時想出了序號的判別方法。時間複雜度O(n2) leetcode
不能AC最後一個樣例超時。吃個飯繼續搞~
def longestPalindrome( s):
string1=s
string2=s[::-1]
len1 = len(string1)
len2 = len(string2)
res = [[0 for i in range(len1+1)] for j in range(len2+1)]
#python 初始化二維數組 [len1+1],[len2+1]
result =0
index=0
for i in range(1,len2+1): #開始從1開始,到len2+1結束
for j in range(1,len1+1): #開始從1開始,到len2+1結束
if string2[i-1] == string1[j-1]:
res[i][j] = res[i-1][j-1]+1
if (result<=res[i][j])& (len(string1)-i==j-res[i][j]):
index=j
result = res[i][j]
string=string1[index-result:index]
return string # 輸出
print(longestPalindrome("adabxxcbada"))
方法二:暴力法
方法三:動態規劃方法
動態規劃的方法,關鍵在於尋找邊界狀態。這個也不能AC,會超時,關於數組邊界的問題,讓我體會了一把python的金穗。
def longestPalindrome(s):
if(s == ""): return s
strlen = len(s)
dp=[[False for i in range(len(s))] for j in range(len(s))]
for i in range(len(s)): dp[i][i] = True
for i in range(len(s)-1): dp[i][i+1] = (s[i] == s[i+1])
left = 0
right = 0
maxnum = 1;
for i in range(len(s)-2,-1,-1): #下左標 ,從0 開始 一定要倒置,否則計算出來的有問題,從小到大推過來的。
for j in range(1,len(s)): #下右標
# P(i,j)=(P(i+1,j−1) and Si==Sj)
if(i != j ) & (j != i+ 1):
dp[i][j] = dp[i+1][j-1] & (s[i] == s[j]);
if(dp[i][j]) & (maxnum < j - i + 1):
maxnum = j - i + 1;
left = i;
right = j;
print(i,j,dp[i][j])
return s[left:right+1];
print( longestPalindrome("ab"))
方法四:中心擴展法
解法五: Manacher’s Algorithm 馬拉車算法
馬拉車算法 Manacher‘s Algorithm 是用來查找一個字符串的最長迴文子串的線性方法,由一個叫 Manacher 的人在 1975 年發明的,這個方法的最大貢獻是在於將時間複雜度提升到了線性。
首先我們解決下奇數和偶數的問題,在每個字符間插入 “#”,並且爲了使得擴展的過程中,到邊界後自動結束,在兩端分別插入 “^” 和 “$”,兩個不可能在字符串中出現的字符,這樣中心擴展的時候,判斷兩端字符是否相等的時候,如果到了邊界就一定會不相等,從而出了循環。經過處理,字符串的長度永遠都是奇數了。
首先我們用一個數組 P 保存從中心擴展的最大個數,而它剛好也是去掉 “#” 的原字符串的總長度。例如下圖中下標是 6 的地方,可以看到 P[ 6 ] 等於 5,所以它是從左邊擴展 5 個字符,相應的右邊也是擴展 5 個字符,也就是 “#c#b#c#b#c#”。而去掉 # 恢復到原來的字符串,變成 “cbcbc”,它的長度剛好也就是 5。
求原字符串下標
用 P 的下標 i 減去 P [ i ],再除以 2,就是原字符串的開頭下標了。
例如我們找到 P[ i ] 的最大值爲 5,也就是迴文串的最大長度是 5,對應的下標是 6,所以原字符串的開頭下標是(6 - 5 )/ 2 = 0。所以我們只需要返回原字符串的第 0 到 第(5 - 1)位就可以了。
求每個 P [ i ]
接下來是算法的關鍵了,它充分利用了迴文串的對稱性。
我們用 C 表示迴文串的中心,用 R 表示迴文串的右邊半徑。所以 R = C + P[ i ]。C 和 R 所對應的迴文串是當前循環中 R 最靠右的迴文串。
讓我們考慮求 P [ i ] 的時候,如下圖。
用 i_mirror 表示當前需要求的第 i 個字符關於 C 對應的下標。