昨天(其实是前天),我阐述了这道题的贪心解法和动态规划解法,今天我来说说另一个方法—分治算法(这个算法的效率并没有贪心和动态规划的方法高效,但是它却和这两种方法有这相同的重要性)
题目
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [0]
输出:0
示例 4:
输入:nums = [-1]
输出:-1
示例 5:
输入:nums = [-100000]
输出:-100000
提示:
1 <= nums.length <= 3 * 104
-105 <= nums[i] <= 105
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。
来源:力扣(LeetCode)
链接:LeetCode No.53 最大子序和
题解 分而治之
我们通过官方代码来讲解:
代码定义了一个函数:get(nums, left, right)表示获取nums数组中从left到right之间的最大子序列和,
所以我们要求的答案就应该是 get(nums, 0, nums.length() - 1),而要用分治的思路实现的话,则是从中间切开形成两个数组leftSub 和 rightSub,而且一直递归形成这些数组,直到当数组长度为1时开始递归回溯:
m = (left + right) >> 1; (m = (left + right) / 2; )
leftSub = get(nums, left, m)
rightSub = get(nums, m + 1, right)
注意:这个地方rightSub的起始位置一定要从m + 1开始,否则会进行无限递归
我们一直进行分治肯定是想要获取一些量为目的的,而这些量是什么呢?如下我们来分析:
每一个数组我们需要获取并维护的都有四个属性:
lSum(从最左端点算起的最大子段和)
rSum(从最右端点算起的最大子段和)
mSum(整段数组的最大子序列)
iSum(整段数组的和)
我一开始很好奇为什么需要lSum 和rSum还有iSum这3个量,然后我发现,一个数组的最大子序列和就是由这几个量生成的,如下来看解释:
如果一个数组从中切成左右两边,则最大子序列mSum有三种可能:
- 全部在左边
- 全部在右边
- 在中间(跨越中间点)
第一种情况,我们会发现,该数组的mSum 就是 左段子数组的 mSum
第二种情况类似,该数组的mSum 就是 右段子数组的 mSum
第三种情况,我们可以得出mSum的值等于左段数组的rSum + 右段数组的lSum
由第三种情况而来rSum,lSum的意义
那么问题来了,rSum,lSum怎么求呢?
求一个数组的rSum,lSum,我们可以分两种情况讨论,我们以lSum为例讨论:
- 等于左段数组的lSum
- 等于左段数组的iSum