給定乙個整數陣列 nums ,找到乙個具有最大和的連續子陣列(子陣列最少包含乙個元素),返回其最大和。
示例:輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 6。
高階:如果你已經實現複雜度為 o(n) 的解loakz法,嘗試使用更為精妙的分治法求解。
思路:首先我們分析題目,我們思考,為什麼最大和的連續子陣列不包含其他的元素而是這幾個呢?因為如果我們想在現有的基礎上去擴充套件當前連續子陣列,相鄰的元素是一定要被加入的,而相鄰元素中可能會減損當前的和。
思路一:
遍曆法,on:
演算法過程:遍歷陣列,用onesum去維護當前元素加起來的和。當onesum出現小於0的情況時,我們把它設為0。然後每次都更新全域性最大值。
class solution:
def maxsubarray(self, nums):
""":type nums: list[int]
:rtype: int
"""#onesum維護當前的和
onesum = 0
maxsum = nums[0]
for i in range(len(nums)):
onesum += nums[i]
maxsum = max(maxsum, onesum)
#出現onesum<0的情況,就設為0,重新累積和
if onesum < 0:
onesum = 0
return maxsum
演算法證明:一開始思考陣列是個空的,把我們每次選乙個nums[i]加入onesum看成當前陣列新增了乙個元素,也就是用動態的眼光去思考。過程很簡單,**很短,但為什麼這樣就能達到效果呢?我們進行的加和是按順序來的,從陣列第乙個開始加。
當我們的i選出來後,加入onesum。這時有2種情況
1)假設我們這個onesum一直大於0,從未被<0過。那也就是說我們計算的每一次的onesum都大於0,而每一次計算的onesum都是包括開頭元素的一段子串行(尾部一直隨i變化)。看似我們沒有考慮所有可能序列,但實際上所有可能的序列都已經被考慮過了。這裡簡單講一下,待會po原文。
a)以當前子串行開頭為開頭,中間任一處結尾的序列。這種情況是一直在掃瞄的,也有一直儲存更新,所以不用怕丟失資訊。
b)以當前子串行結尾為結尾,中間任一處開頭的序列。這種情況一定的和小於以當前子串行開頭為開頭,結尾為結尾的序列。因為前面缺失的那一段經過我們的前提,它也是加和大於0的。
c)以中間元素為開頭和結尾的序列。和小於以當前子串行開頭為開頭,此分序列結尾為結尾的序列。因為前面缺失的那一段經過我們的前提,它也是加和大於0的。
2)出現小於0的情況,就說明我們當前形成的這個子序是第一次出現小於0的情況。現在至少我們要新形成的連續陣列不能在整個的包括之前的連續子序了,因為我們在之前的那個連續子序裡加出了<0的情況。但問題是我們需不需要保留一些呢?是不是所有以當前子序結尾為結尾的任意開頭的子序都要被捨棄呢?答案是是的,因為那一段也一定小於0,因為那一段的加和會小於以當前子序開頭為開頭,當前子序結尾為結尾的序列(見前面證明)。於是拋棄掉它們,重新開始新的子序。
思路二:
動態規劃 on
演算法過程:
設sum[i]為以第i個元素結尾的最大的連續子陣列的和。假設對於元素i,所有以它前面的元素結尾的子陣列的長度都已經求得,那麼以第i個元素結尾且和最大的連續子陣列實際上,要麼是以第i-1個元素結尾且和最大的連續子陣列加上這個元素,要麼是只包含第i個元素,即sum[i]= max(sum[i-1] + a[i], a[i])。可以通過判斷sum[i-1] + a[i]是否大於a[i]來做選擇,而這實際上等價於判斷su程式設計客棧m[i-1]是否大於0。由於每次運算只需要前一次的結果,因此並不需要像普通的動態規劃那樣保留之前所有的計算結果,只需要保留上一次的即可,因此演算法的時間和空間複雜度都很小
class solution:
def maxsubarray(self, nums):
"""
:type nums: list[int]
:rtype: int
"""
length=len(nums)
for i in range(1程式設計客棧,length):
#當前值的大小與前面的值之和比較,若當前值更大,則取當前值,捨棄前面的值之和
submaxsum=max(nums[i]+nums[i-1],nums[i])
nums[i]=submaxsum#將當前和最大的賦給nums[i],新的nums儲存的為和值
return max(nums)
演算法證明:這道題的**我直接使用了題目資料中的nums陣列,因為只要遍歷一遍。nums[i]表示的是以當前這第i號元素結尾(看清了一定要包含當前的這個i)的話,最大的值無非就是看以i-1結尾的最大和的子序能不能加上我這個nums[i],如果nums[i]>0的話,則加上。注意我**中沒有顯式地去這樣判斷,不過我的max表達的就是這個意思,然後我們把nums[i]確定下來。
思路三:
分治遞迴
演算法過程:
分治法,最大子序和要麼在左半部分,要麼在右半部分,要麼就橫跨兩部分(即包括左半部分的最後乙個元素,和右半部分的第乙個元素)。返回這三種情況的最大值即可。第三種情況,其中包括左半部分最後乙個元素的情形,需要挨個往前遍歷,更新最大值。包含右半部分的第乙個元素的情況類似。總的時間複雜度o(nlogn)
class solution(object):
def maxsubarray(self, nums):
#主函式
left = 0
#左右邊界
right = len(nums)-1
#求最大和
maxsum = self.divide(nums,left,right)
return maxsum
def divide(self,nums,left,right):
#如果只有乙個元素就返回
if left==right:
return nums[left]
#確立中心點
center = (left+right)//2
#求子序在中心點左邊的和
leftmaxsum = self.divide(nums,left,center)
www.cppcns.com#求子序在中心點右邊的和
rightmaxsum = self.divide(nums,center+1,right)
#求子序橫跨2邊的和,分成左邊界和和右邊界和
leftbordersum = nums[center]
leftsum = nums[center]
for i in range(center-1,left-1,-1):
leftsum += nums[i]
if leftsum>leftbordersum:
#不斷更新左區塊的最大值
leftbordersum = leftsum
rightbordersum = nums[center+1]
rightsum = nums[center+1]
for i in range(center+2,right+1):
rightsum += nums[i]
if rightsum>rightbordersum:
#不斷更新右區塊的最大值
rightbordersum = rightsum
#左邊界的和 + 右邊那塊的和
bordersum = leftbordersum + rightbordersum
return max(leftmaxsum,rightmaxsum,bordersum)
演算法證明:
總的來說還是超級巧妙的。不斷的切不斷的切陣列,把一塊陣列看成左中右三個部分。實際上這有點像列舉,但我們在列舉時利用了二分的思路,優化了很多。所以列舉當然可以達到我們的目標了,因為我們不斷在計算以一定包括中間節點的子序的最大和。
總結:今天寫了很多很多,都沒時間複習了。。。但是收穫還是很大的。比如動態規劃,不一定一定要建立陣列然後返回陣列的最後一項,動態規劃其實是很靈活的。但是動態規劃的每一項代表的意義要想好。
分治遞迴,實際在幫我們算所有陣列只不過用了二分的思路優化。
本文標題: python實現最大子序和(分治+動態規劃)
本文位址: /jiaoben/python/264729.html
最大子序和 DP,分治
給定乙個整數陣列 nums 找到乙個具有最大和的連續子陣列 子陣列最少包含乙個元素 返回其最大和。示例 輸入 2,1,3,4,1,2,1,5,4 輸出 6 解釋 連續子陣列 4,1,2,1 的和最大,為 6。高階 如果你已經實現複雜度為 o n 的解法,嘗試使用更為精妙的分治法求解。以a 0 結尾的...
最大子序和(最大子段和(python))
1 暴力求解 基本思路就是遍歷一遍,用兩個變數,乙個記錄最大的和,乙個記錄當前的和。時間複雜度 o n 3 80 ms 1 class solution def maxsubarray self,nums list int int tmp nums 0 max tmp n len nums for ...
leetcode最大子序和python
給定乙個整數陣列 nums 找到乙個具有最大和的連續子陣列 子陣列最少包含乙個元素 返回其最大和。示例 輸入 2,1,3,4,1,2,1,5,4 輸出 6 解釋 連續子陣列 4,1,2,1 的和最大,為 6。題解 1 首先初始化兩個變數sums和ans,sums用來存放nums i 位置上的最大子連...