1. 程式人生 > >LeetCode5最長迴文子串

LeetCode5最長迴文子串

這兩天被這個題弄得是有些崩潰了,因為之前試的兩種方法都是超時了,所以弄得後面都有些不想弄了。還好有度娘,最後用的是從中間往兩邊擴充套件的方法得此解決,真的是如釋重負啊!廢話不說,講正文貼程式碼。

題目如下:

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

示例 1:

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

示例 2:

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

方法一:暴力法

有些人會忍不住提出一個快速的解決方案,不幸的是,這個解決方案有缺陷(但是可以很容易地糾正):

反轉 SS,使之變成 S'S′。找到 SS 和 S'S′ 之間最長的公共子串,這也必然是最長的迴文子串。

這似乎是可行的,讓我們看看下面的一些例子。

例如,S = {“caba”}S=“caba” , S' = {“abac”}S′=“abac”:

SS 以及 S'S′ 之間的最長公共子串為 {“aba”}“aba”,恰恰是答案。

讓我們嘗試一下這個例子:S ={“abacdfgdcaba”}S=“abacdfgdcaba” , S' = {“abacdgfdcaba”}S′=“abacdgfdcaba”:

SS 以及 S'S′ 之間的最長公共子串為{“abacd”}“abacd”,顯然,這不是迴文。

這種方法確實最好理解的額,但效率確實是低。所以放在編輯器裡面跑,都是直接報超出時間限制的錯誤的,這是最dan疼的

def longestPalindrome(self, s):
        s_inverse = s[::-1]
        max = 0
        maxStr = ""
        if len(s) < 2:
            return s
        for start in range(len(s)):
            for end in range(start+1, len(s)+1):
                if s.count(s_inverse[start:end]) > 0:
                    index = s.index(s_inverse[start:end])
                    start_inverse = len(s) - end
                    if (end - start > max) and (index == start_inverse):
                        max = end - start
                        maxStr = s_inverse[start:end]
        return maxStr

這種方法是巧妙地運用了python 字串的一些內建函式執行的,程式碼量很少,但跑起來時間不短

大概就是報這樣的提醒,就是說你的程式沒問題,但是太low了,到後面它都不願意測試了

    def longestPalindrome(self, s):
        max = 0
        maxStr = ""
        if len(s) < 2:
            return s
        for start in range(len(s)):
            for end in range(len(s)-1, start-1, -1):
                start_copy = start
                end_copy = end
                while s[start_copy] == s[end_copy] and start_copy < end and end_copy >         
                      start:
                    start_copy += 1
                    end_copy -= 1
                if start_copy == end and end_copy == start:
                    if end - start >= max:
                        max = end - start
                        maxStr = s[start: end+1]
        return maxStr

這個方法是借鑑了動態規劃的思想,但是也是效率過低,報如上的錯誤,貼出來可供參考

方法二:動態規劃

為了改進暴力法,我們首先觀察如何避免在驗證迴文時進行不必要的重複計算。考慮{“ababa”}“ababa” 這個示例。如果我們已經知道 {“bab”}“bab” 是迴文,那麼很明顯,{“ababa”}“ababa” 一定是迴文,因為它的左首字母和右尾字母是相同的。

我們給出 P(i,j)P(i,j) 的定義如下:

P(i,j) = \begin{cases} \text{true,} &\quad\text{如果子串} S_i \dots S_j \text{是迴文子串}\\ \text{false,} &\quad\text{其它情況} \end{cases}P(i,j)={true,false,​如果子串Si​…Sj​是迴文子串其它情況​

因此,

P(i, j) = ( P(i+1, j-1) \text{ and } S_i == S_j )P(i,j)=(P(i+1,j−1) and Si​==Sj​)

基本示例如下:

P(i, i) = trueP(i,i)=true

P(i, i+1) = ( S_i == S_{i+1} )P(i,i+1)=(Si​==Si+1​)

這產生了一個直觀的動態規劃解法,我們首先初始化一字母和二字母的迴文,然後找到所有三字母迴文,並依此類推…

複雜度分析

  • 時間複雜度:O(n^2)O(n2), 這裡給出我們的執行時間複雜度為 O(n^2)O(n2) 。

  • 空間複雜度:O(n^2)O(n2), 該方法使用 O(n^2)O(n2) 的空間來儲存表。

程式碼如下:

    def longestPalindrome(self, s):
        dpArray = np.zeros([1000, 1000])
        maxIndex = -1
        maxStr = ""
        if len(s) < 2:
            return s
        dpArray[0][0] = 1
        for end in range(1, len(s)):
            dpArray[end][end] = 1
            for start in range(end+1):
                if end == start+1 and s[end] == s[start]:
                    dpArray[start][end] = 1
                if end > start + 1:
                    dpArray[start][end] = dpArray[start+1][end-1] and (s[end] == s[start])
                if end - start >= maxIndex and dpArray[start][end]:
                    maxIndex = end - start
                    maxStr = s[start:end+1]
                    print(maxStr)
        print(dpArray[0:len(s), 0:len(s)])
        return maxStr

看起來確實是牛逼了一些,但不知為何在我的電腦上跑,它還是說超出時間限制,當時真的是快把我給氣炸了

方法三:中心擴充套件演算法

事實上,只需使用恆定的空間,我們就可以在 O(n^2)O(n2) 的時間內解決這個問題。

我們觀察到迴文中心的兩側互為映象。因此,迴文可以從它的中心展開,並且只有 2n - 1個這樣的中心。

你可能會問,為什麼會是 2n - 1個,而不是 n 箇中心?原因在於所含字母數為偶數的迴文的中心可以處於兩字母之間(例如{“abba”}“abba” 的中心在兩個 {‘b’}‘b’ 之間)。

程式碼如下:

    def longestPalindrome(self, s):
        maxLen = 0
        maxStr1 = ""
        for start_current in range(len(s)):
            if len(s) - start_current <= int(maxLen/2):
                break
            start_head = start_current
            start_last = start_current + 1
            while start_head >= 0 and start_last < len(s) and s[start_head] == s[start_last]:
                start_head -= 1
                start_last += 1
            if start_last - start_head - 1 >= maxLen:
                maxLen = start_last - start_head - 1
                maxStr1 = s[start_head + 1: start_last]
            start_head = start_current
            start_last = start_current
            while start_last < len(s) and start_head >= 0 and s[start_head] == s[start_last]:
                start_head -= 1
                start_last += 1
            if start_last - start_head - 1 >= maxLen:
                maxLen = start_last - start_head - 1
                maxStr1 = s[start_head + 1: start_last]
        return maxStr1

到了這種方法總算是給跑出來了,而且它的速度也算是中等吧!真的是一把鼻涕一把淚啊,本來想再把速度給提高的,但是被之前的失敗經歷給打擊了,容我緩一段時間再來剛,最後把它的執行時間給貼出來