题目
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
solution_1
思路:遍历法。遍历数组,用onesum去维护当前元素加起来的和。当onesum出现小于0的情况时,我们把它设为0。然后每次都更新全局最大值。
解释:
一开始思考数组是个空的,把我们每次选一个nums[i]加入onesum看成当前数组新增了一个元素,也就是用动态的眼光去思考。我们进行的加和是按顺序来的,从数组第一个开始加。
当我们的i ii选出来后,加入onesum。这时有2种情况
1.假设我们这个onesum一直大于0,从未被<0过。那也就是说我们计算的每一次的onesum都大于0,而每一次计算的onesum都是包括开头元素的一段子序列(尾部一直随i ii变化)。看似我们没有考虑所有可能序列,但实际上所有可能的序列都已经被考虑过了。
1.1以当前子序列开头为开头,中间任一处结尾的序列。这种情况是一直在扫描的,也有一直保存更新,所以不用怕丢失信息。
1.2以当前子序列结尾为结尾,中间任一处开头的序列。这种情况一定的和小于以当前子序列开头为开头,结尾为结尾的序列。因为前面缺失的那一段经过我们的前提,它也是加和大于0的。
1.3以中间元素为开头和结尾的序列。和小于以当前子序列开头为开头,此分序列结尾为结尾的序列。因为前面缺失的那一段经过我们的前提,它也是加和大于0的。
2.出现小于0的情况,就说明我们当前形成的这个子序是第一次出现小于0的情况。现在至少我们要新形成的连续数组不能在整个的包括之前的连续子序了,因为我们在之前的那个连续子序里加出了<0的情况。但问题是我们需不需要保留一些呢?是不是所有以当前子序结尾为结尾的任意开头的子序都要被舍弃呢?答案是是的,因为那一段也一定小于0,因为那一段的加和会小于以当前子序开头为开头,当前子序结尾为结尾的序列。于是抛弃掉它们,重新开始新的子序。
结果:执行用时:88 ms
排名:战胜77.78%
代码如下
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 遍历法
onesum = 0
maxsum = nums[0]
for i in range(len(nums)):
onesum += nums[i]
maxsum = max(onesum, maxsum)
if onesum < 0:
onesum =0
return maxsum
solution_2
思路:动态规划。设sum[i]为以第i ii个元素结尾的最大的连续子数组的和。假设对于元素i ii,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i ii个元素结尾且和最大的连续子数组实际上,要么是以第i − 1 i-1i−1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i ii个元素,即sum[i]
= max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。
结果:执行用时:84 ms
排名:战胜80.28%
代码如下
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 动态规划
for i in range(1, len(nums)):
maxsum = max(nums[i-1]+nums[i], nums[i])
nums[i]=maxsum
return max(nums)
solution_3
思路:分治法,最大子序和要么在左半部分,要么在右半部分,要么就横跨两部分(即包括左半部分的最后一个元素,和右半部分的第一个元素)。返回这三种情况的最大值即可。第三种情况,其中包括左半部分最后一个元素的情形,需要挨个往前遍历,更新最大值。包含右半部分的第一个元素的情况类似。总的时间复杂度O(nlogn)。
结果:执行用时:132 ms
排名:战胜24.76%
代码如下
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 分治法
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
left_maxsum = self.divide(nums, left, center)
right_maxsum = self.divide(nums, center+1, right)
left_border_sum = nums[center]
left_sum = nums[center]
for i in range(center-1, left-1, -1):
left_sum += nums[i]
if left_sum>left_border_sum:
left_border_sum = left_sum
right_border_sum = nums[center+1]
right_sum = nums[center+1]
for i in range(center+2, right+1):
right_sum += nums[i]
if right_sum>right_border_sum:
right_border_sum = right_sum
border_maxsum = left_border_sum + right_border_sum
return max(left_maxsum, right_maxsum, border_maxsum)