1. 程式人生 > >20.最大子序和-Leetcode 053(python)

20.最大子序和-Leetcode 053(python)

  • 題目描述

給定一個整數陣列 nums ,找到一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。

  • 示例

輸入: [-2,1,-3,4,-1,2,1,-5,4], 輸出: 6 解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 6。

進階:

如果你已經實現複雜度為 O(n) 的解法,嘗試使用更為精妙的分治法求解。

  • 解決思路一

自己想到的方法是比較簡單的。遍歷所有的元素,以當前元素為子序列的頭,陣列中該元素之後的任意元素為子序列的尾,計算每個子序列的和,複雜度為O(N^2),結果超時了,沒有通過。

  • 程式碼一
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        maxsum = nums[0]

        length = len(nums)
        if length == 1:
            return nums[0]
        else:
            for i in range(length):
                onesum = 0
                for j in range(i,length):
                    onesum = onesum + nums[j]
                    if onesum > maxsum:
                        maxsum = onesum

                    
            return maxsum
  • 解決思路二(來自網路)

仍舊是遍歷法,用onesum來維護遍歷到當前元素時,前邊的元素組成的子序和,如果onesum出現小於0的情況,就讓它等於0;每次都更新全域性最大值。

根據:引用自他人部落格

據說這道題是《程式設計珠機》裡面的題目,叫做掃描法,速度最快,掃描一次就求出結果,複雜度是O(n)。書中說,這個演算法是一個統計學家提出的。 這個演算法如此精煉簡單,而且複雜度只有線性。但是我想,能想出來卻非常困難,而且證明也不簡單。在這裡,我斗膽寫出自己證明的想法: 關於這道題的證明,我的思路是去證明這樣的掃描法包含了所有n^2種情況,即所有未顯示列出的子陣列都可以在本題的掃描過程中被拋棄。 1 首先,假設演算法掃描到某個地方時,始終未出現加和小於等於0的情況。 我們可以把所有子陣列(實際上為當前掃描過的元素所組成的子陣列)列為三種: 1.1 以開頭元素為開頭,結尾為任一的子陣列 1.2 以結尾元素為結尾,開頭為任一的子陣列 1.3 開頭和結尾都不等於當前開頭結尾的所有子陣列 1.1由於遍歷過程中已經掃描,所以演算法已經考慮了。1.2確實沒考慮,但我們隨便找到1.2中的某一個數組,可知,從開頭元素到這個1.2中的陣列的加和大於0(因為如果小於0就說明掃描過程中遇到小於0的情況,不包括在大前提1之內),那麼這個和一定小於從開頭到這個1.2陣列結尾的和。故此種情況可捨棄 1.3 可以以1.2同樣的方法證明,因為我們的結尾已經列舉了所有的情況,那麼每一種情況和1.2是相同的,故也可以捨棄。 2 如果當前加和出現小於等於0的情況,且是第一次出現,可知前面所有的情況加和都不為0 一個很直觀的結論是,如果子段和小於0,我們可以拋棄,但問題是是不是他的所有以此子段結尾為結尾而開頭任意的子段也需要拋棄呢? 答案是肯定的。因為以此子段開頭為開頭而結尾任意的子段加和都大於0(情況2的前提),所以這些子段的和是小於當前子段的,也就是小於0的,對於後面也是需要拋棄的。也就是說,所有以之前的所有元素為開頭而以當前結尾之後元素為結尾的陣列都可以拋棄了。 而對於後面拋棄後的陣列,則可以同樣遞迴地用1 2兩個大情況進行分析,於是得證。

說實話看完到現在也有點糊里糊塗的,拿一個具體陣列來進行操作一下對這個過程的理解能更清楚一點,但是解釋不了上邊這麼詳細,理解起來就是另一個博主的博文:

當我們加上一個正數時,和會增加;當我們加上一個負數時,和會減少。如果當前得到的和是個負數,那麼這個和在接下來的累加中應該拋棄並重新清零,不然的話這個負數將會減少接下來的和。

演算法複雜度為O(N)

  • 程式碼二
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        onesum = 0
        maxsum = nums[0]
        
        for i in range(len(nums)):
            #onesum維護當前和
            onesum += nums[i]
            
            #更新當前的全域性最大值
            maxsum = max(maxsum,onesum)
            #如果onesum<0,就清空當前的累積和,設為0,但是並不會影響最後的maxsum,因為上一句程式碼已經在考慮onesum的情況下,把maxsum更新過了
            if onesum < 0:
                onesum = 0
                
        return maxsum
 
  • 解決思路三

如果我們用sum[i]存放以元素i為結尾的最大子序列的和,那麼我們每次都只需要比較一下sum[i-1]+num[i]和num[i]的大小。因為對於第i個元素來說,如果以第i-1個元素為結尾的最大的子序列之和已經求得,那麼,以第i個元素為結尾且和最大的連續子序列,要麼就是以第i-1個元素為結尾的有最大和的子序列加上第i個元素(第i個元素大於0),要麼就是第i個元素(sum[i-1]小於0)。即sum[i] = max(sum[i-1]+num[i],num[i])。

由於每次運算只需要用到上一次的運算結果,所以仍舊使用nums陣列來存放以當前元素結尾的最大子序列的和,在程式執行的最後,找出nums裡邊的最大值即可。

演算法複雜度為O(N)

  • 程式碼三
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        #遍歷從第二個元素開始,才能有前一個元素的概念存在
        for i in range(1,len(nums)):
            
            subMaxsum = max(nums[i-1]+nums[i],nums[i])
            
            #nums[i]的元素用來存放以第i個元素為結尾的最大子序和
            nums[i] = subMaxsum
        
        #返回所有子序和的最大值
        return max(nums)