一、题目
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
二、思路-分治法
分治法是将整个数组切分成几个小组,然后每个小组再切分成几个更小的小组,一直到不能继续切分也就是只剩一个数字为止。每个小组会计算出最优值,汇报给上一级的小组,一级一级汇报,上级拿到下级的汇报找到最大值,得到最终的结果。和归并排序的算法类似,先切分,再合并结果。
这个问题中的关键就是如何切分这些组合才能使每个小组之间不会有重复的组合(有重复的组合意味着有重复的计算量),这个问题应该困扰了不少的同学,我在学习理解的时候也花了不少时间。
首先是切分分组方法,就这个案例中的例子来,我们有一个数组 [-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],这样就保证这个组合里面的任何序列都不会和上面两个重复)
以上的三个组合内的序列没有任何的重复的部分,而且一起构成所有子序列的全集,计算出这个三个子集合的最大值,然后取其中的最大值,就是这个问题的答案了。
然而前两个子组合可以用递归来解决,一个函数就搞定,第三个跨中心的组合应该怎么计算最大值呢?
答案就是先计算左边序列里面的包含最右边元素的子序列的最大值,也就是从左边序列的最右边元素向左一个一个累加起来,找出累加过程中每次累加的最大值,就是左边序列的最大值。
同理找出右边序列的最大值,就得到了右边子序列的最大值。左右两边的最大值相加,就是包含这两个元素的子序列的最大值。
在计算过程中,累加和比较的过程是关键操作,一个长度为 n 的数组在递归的每一层都会进行 n 次操作,分治法的递归层级在 logN 级别,所以整体的时间复杂度是 O(nlogn),在时间效率上不如动态规划的 O(n) 复杂度。
分治法的思路是这样的,其实也是分类讨论。
连续子序列的最大和主要由这三部分子区间里元素的最大和得到:
第 1 部分:子区间 [left, mid];
第 2 部分:子区间 [mid + 1, right];
第 3 部分:包含子区间[mid , mid + 1]的子区间,即 nums[mid] 与nums[mid + 1]一定会被选取。
对它们三者求最大值即可。‘
三、分治法代码
class Solution(object):
def maxSubArray(self, nums):
return self.maxSubArrayDivideWithBorder(nums, 0, len(nums)-1)
def maxSubArrayDivideWithBorder(self,nums, start, end):
if start == end:
# 只有一个元素,也就是递归的结束情况
return nums[start]
# 计算中间值
center = (start + end) / 2
leftMax = self. maxSubArrayDivideWithBorder(nums, start, center) # 计算左侧子序列最大值
rightMax = self.maxSubArrayDivideWithBorder(nums, center + 1, end) # 计算右侧子序列最大值
# 下面计算横跨两个子序列的最大值
# 计算包含左侧子序列最后一个元素的子序列最大值
leftCrossMax = 0 # 初始化一个值
leftCrossSum = 0
for i in range(center, start-1,-1):
leftCrossSum += nums[i]
leftCrossMax = max(leftCrossSum, leftCrossMax)
# 计算包含右侧子序列最后一个元素的子序列最大值
rightCrossMax = nums[center+1]
rightCrossSum = 0
for i in range (center + 1, end+1):
rightCrossSum += nums[i]
rightCrossMax = max(rightCrossSum, rightCrossMax)
# 计算跨中心的子序列的最大值
crossMax = leftCrossMax + rightCrossMax
# 比较三者,返回最大值
return max(crossMax, max(leftMax, rightMax))
四、分治法运行结果