給定一個整數陣列,找出總和最大的連續數列,並返回總和。

我家大寶最可愛發表於2020-10-15

題目:給定一個整數陣列,找出總和最大的連續數列,並返回總和。

這個題目初看不會,看了解答還是不會,又看了解答終於想明白了,關鍵點就一個 連續數列
什麼叫做連續數列,就是連在一起的資料。

例如有一個數列,我們要找其中連續的子序列,假設-1是我們最大連續子序列的最後一個數字,然後我們求出以這個數字為結尾的所有的連續的子序列,然後把每一個子序列求和

最後元素子序列和
原始序列-21-34-121-54
子序列1-1-1
子序列24-13
子序列3-34-10
子序列41-34-11
子序列5-21-34-1-1

然後我們再假設2是最大連續子序列的最後一個元素,再來求一遍每個子序列的和

最後元素子序列和
原始序列-21-34-121-54
子序列122
子序列1-121
子序列24-125
子序列3-34-12-2
子序列41-34-123
子序列5-21-34-121

有點感覺了吧,這其實就是把所有連續的子序列全部羅列一邊,然後求出所有的連續的子序列的和。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if nums == []:
            return 0
        dp = []
        for end in range(1 ,len(nums)):
            for start in reversed(range(end+1)):
                dp.append(sum(nums[start:end])+nums[end])
        return max(dp + nums)

同樣的我們也可以以某個元素為開始計算連續子序列的和。

動態規劃

緊接著就是我們的動態規劃出場了,其實有點經驗的人看到這個題目就知道使用動態規劃,只是轉移方程不會寫。
加入我們已經知道以第i-1為結尾的最大子序列和

dp[i-1]dp[i]子序列和
原始序列-21-34-121-54
子序列1-21-34-13
子序列2-21-34-125

因為是連續的(包含nums[i]以及前面的)所以直接可以知道dp[i] = dp[i-1]+nums[i],這裡有一個很有趣的地方

dp[i-1]nums[i]要不要加上
都是正的加上肯定變大啊,跟前面的子序列連上變成更長的子序列
前面是正的,趕快加上前面的連續子序列,讓自己變的大一點
本來就是負的了,再加上dp[i-1]的負數就更小了,不加,獨立門戶,自己就是連續子序列
自己是正的,為什麼要跟前面的負數連在一起,獨立門戶,自己就是連續子序列

哈哈,我們們的轉移方程就出現了,加上去比自己大,那就加上,如果加上去比自己還小了,肯定就不加了啊

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if nums == []:
            return 0
        for i in range(1 ,len(nums)):
        	nums[i] = max(nums[i-1]+nums[i],nums[i])
        return max(nums)

這個問題還可以更精簡,如果前面是正的,我們們就加上,如果前面是負的,那麼當前的最大子序列和就是自身nums[i]

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if nums == []:
            return 0
        res,s = -float('inf'),0
        for v in nums:
            s += v
            res = max(res,s)
            if(s < 0): s = 0
        return res

分治法

其實做這個題目我就是想要學習一下分治演算法,沒想到前面寫了這麼多。分治求解的思想非常的簡單,我們的連續子序列有三種可能

  • 和最大的子序列存在我們整個序列的左側
  • 和最大的子序列存在我們整個序列的左側
  • 橫跨序列左半部分和右半部分:也就是說,中間這個數字是左邊子序列的結尾,是右邊子序列的開始,我們算一下以這個數字開始和結尾的左右子序列最大和,然後求和即可,說起來有點繞,看圖
    比較三者的大小,最大者即為所求的最大子序列和
左邊中間右邊最大和
最大子序列子這邊 max_leftmax_left
以v結尾的和最大的子序列 max_v_leftv以v開始的和最大的子序列 max_v_rightmax_v_left + v + max_v_right
最大子序列子這邊 max_rightmax_right

整體就是這個樣子的,然後我們們寫一個虛擬碼試試

def maxSubArray(nums):
	l,r = 0,len(nums)
	mid = (l+r) // 2
	# 最大和子序列在左側
	max_left = maxSubArray(nums[:mid])
	# 最大和子序列在右側
	max_right = maxSubArray(nums[mid:])
	# # 最大和子序列兩側都包含元素
	# 首先計算以mid為結尾的左側最大子序列的和,這個最開始的那種方法一模一樣啊
	# 即,一個一個元素增加求每一個子序列的和
	left_res,right_res = 0,0
	left_max,rigth_max = -float('inf'),-float('inf')
	# 以mid結束的前面所有連續子序列的和,求其中的最大(這裡是去除了mid的值,要不然這裡會被計算一次)
	for v in reversed(nums[:mid]):
		left_res += v
		left_max = max(left_max,left_res)
	
	# 以mid開始的前面所有連續子序列的和,求其中的最大 (然後這裡還會被計算一次,所以上面去掉了mid)
	for v in nums[mid:]:
		right_res += v
		rigth_max = max(rigth_max,right_res)
		
	max_mid = left_max + rigth_max
	
	return max(max_left,max_right,max_mid)
	

相關文章