53. 最大子序和
题目描述
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
题解
**1.方法动态规划
> 第 1 步:定义状态
既然一个连续子数组一定要以一个数作为结尾,那么我们就将状态定义成如下。
dp[i]:表示以 nums[i] 结尾的连续子数组的最大和。
第 2 步:思考状态转移方程
根据状态的定义,由于 nums[i] 一定会被选取,并且 dp[i] 所表示的连续子序列与 dp[i - 1] 所表示的连续子序列(有可能)就差一个 nums[i] 。
假设数组 nums 全是正数,那么一定有 dp[i] = dp[i - 1] + nums[i]。
在一般情况下 dp[i - 1] 有可能是负数,例如前几个数都是负数,突然来了一个正数。
于是分类讨论:
如果 dp[i - 1] >= 0,那么可以把 nums[i] 直接接在 dp[i - 1] 表示的那个数组的后面。
如果 dp[i - 1] < 0,那么加上前面的数反而越来越小了,于是“另起炉灶”,单独的一个 nums[i],就是 dp[i]。
第一个子组合是以第一个数字结尾的连续序列,也就是[-2],最大值-2
第二个子组合是以第二个数字结尾的连续序列,也就是[-2,1], [1],最大值1
第三个子组合是以第三个数字结尾的连续序列,也就是[-2,1]+[,3], [1]+[3], [3],最大值4
继承子组合二得到的序列,也就是[-2,1,3], [1,3] (最大值 = 第二个组合的最大值 + 第三个数字)
单独第三个数字的序列,也就是[3] (最大值 = 第三个数字)**
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
maxSum = nums[0]
maxContinuousSum = 0 # 到当前值的最大连续和
for num in nums:
if maxContinuousSum > 0:
maxContinuousSum = maxContinuousSum + num
else:
maxContinuousSum = num
maxSum = max(maxSum, maxContinuousSum)
return maxSum
from typing import List
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
size = len(nums)
if size == 0:
return 0
dp = [0 for _ in range(size)] #全部都是0
dp[0] = nums[0]
for i in range(1, size):
dp[i] = max(dp[i - 1] + nums[i], nums[i])
return max(dp)
复杂度分析
时间复杂度:O(N)。只遍历一次数组。
空间复杂度:O(1),只使用了常数空间。
- 分治法:
治法是将整个数组切分成几个小组,每个小组然后再切分成几个更小的小组,一直到不能继续切分也就是只剩一个数字为止。然后每个小组会计算出最优值,汇报给上一级的小组,一级一级汇报,上级拿到下级的汇报找到最大值,得到最终的结果。和合并排序的算法类似,先切分,再合并结果。
这个问题中的关键就是如何切分这些组合才能使每个小组之间不会有重复的组合(有重复的组合意味着有重复的计算量),这个问题应该困扰了不少的同学,我在学习理解的时候也花了不少时间。
首先是切分分组方法,就这个案例中的例子来,我们有一个数组[-2,1,-3,4,-1,2,1,-5,4],一共有9个元素,我们center=(start+ end) / 2这个原则,得到中间元素的索引为4,也就是-1,拆分成三个组合:
[-2,1,-3,4,-1]以及它的子序列(在-1左边的并且包含它的为一组)
[2,1,-5,4]以及它的子序列(在-1右边不包含它的为一组)
任何包含-1以及它右边元素2的序列为一组(换言之就是包含左边序列的最右边元素以及右边序列最左边元素的序列,比如[4,-1,2,1],这样就保证这个组合里面的任何序列都不会和上面两个重复)
以上的三个组合内的序列没有任何的重复的部分,而且一起构成所有子序列的全集,计算出这个三个子集合的最大值,然后取其中的最大值,就是这个问题的答案了。
from typing import List
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
size = len(nums)
if size == 0:
return 0
return self.__max_sub_array(nums, 0, size - 1)
def __max_sub_array(self, nums, left, right):
if left == right:
return nums[left]
mid = (left + right) >> 1
return max(self.__max_sub_array(nums, left, mid),
self.__max_sub_array(nums, mid + 1, right),
self.__max_cross_array(nums, left, mid, right))
def __max_cross_array(self, nums, left, mid, right):
# 一定包含 nums[mid] 元素的最大连续子数组的和,
# 思路是看看左边"扩散到底",得到一个最大数,右边"扩散到底"得到一个最大数
# 然后再加上中间数
left_sum_max = 0
start_left = mid - 1
s1 = 0
while start_left >= left:
s1 += nums[start_left]
left_sum_max = max(left_sum_max, s1)
start_left -= 1
right_sum_max = 0
start_right = mid + 1
s2 = 0
while start_right <= right:
s2 += nums[start_right]
right_sum_max = max(right_sum_max, s2)
start_right += 1
return left_sum_max + nums[mid] + right_sum_max
复杂度分析:
时间复杂度:O(NlogN),这里递归的深度是对数级别的,每一层需要遍历一遍数组(或者数组的一半、四分之一)。
空间复杂度:O(1),仅需要常数个空间用于选取最大值。