题目描述
示例
方法一:动态规划
思路和算法:
我们用 f(i) 代表以第 i 个数结尾的 【连续子数组的最大和】,那么我们要求的就是:
因此我们只需要求出每个位置的 f(i) ,然后返回 f 数组中的最大值即可。
如何求 f(i)呢?我们可以考虑 nums[i] 单独成为一段还是加入 f(i-1) 对应的那一段,于是我们可以得到如下的动态规划转移方程:
考虑到 f(i) 只和 f(i-1) 相关,于是我们可以只用一个变量 pre 来维护当前 f(i) 的 f(i-1)的值是多少,从而让空间复杂度从O(n)降到O(1),这有点类似滚动数组的思想。
代码:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
pre = 0
maxAns = nums[0]
for x in nums:
pre = max(pre + x, x)
maxAns = max(maxAns, pre)
return maxAns
方法二:分治(线段树)
思路和算法:
我们定义一个操作 get(a, l, r) 表示查询 a 序列 [l, r] 区间内的最大子段和,那么我们最终要求的答案就是 get(nums, 0, len(nums)-1)。分治实现此操作:左子区间[l, m],右子区间[m+1, r]。维护以下四个量:
- lSum 表示 [l, r] 内以 l 为左端点的最大子段和
- rSum 表示 [l, r] 内以 r 为右端点的最大子段和
- mSum 表示 [l, r] 内的最大子段和
- iSum 表示 [l, r] 的区间和
对于长度为1的区间 [i ,i],四个量的值都和 nums[i] 相等。对于长度大于1的区间:
- 首先最好维护的是 iSum,区间 [l, r] 的 iSum 就等于【左子区间】的 iSum 加上【右子区间】的 iSum。
- 对于 [l, r] 的 lSum ,存在两种可能,它要么等于【左子区间】的 lSum,要么等于【左子区间】的 iSum 加上【右子区间】的 lSum,两者取大。
- 对于 [l, r] 的 rSum,同理,它要么等于【右子区间】的 rSum,要么等于【右子区间】的 iSum 加上【左子区间】的 rSum,两者取大。
- 当计算好上面的三个量之后,就很好计算 [l, r] 的 mSum 了。我们可以考虑 [l, r] 的 mSum 对应的区间是否跨越 m——它可能不跨越 m,也就是说 [l, r] 的 mSum 可能是【左子区间】的 mSum 和【右子区间】的 mSum 中的一个;它也可能跨越 m,可能是【左子区间】的 rSum 和【右子区间】的 lSum 求和。三者取大。
这样问题就得到了解决。
代码:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
class Status(object):
lSum = rSum = mSum = iSum = 0
def __init__(self, lSum, rSum, mSum, iSum):
self.lSum = lSum
self.rSum = rSum
self.mSum = mSum
self.iSum = iSum
def pushUp(l, r):
iSum = l.iSum + r.iSum
lSum = max(l.lSum, l.iSum + r.lSum)
rSum = max(r.rSum, r.iSum + l.rSum)
mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum)
return Status(lSum, rSum, mSum, iSum)
def getInfo(a, l, r):
if l == r:
return Status(a[l], a[l], a[l], a[l])
# 分治
m = (l + r) >> 1
lSub = getInfo(a, l, m)
rSub = getInfo(a, m + 1, r)
return pushUp(lSub, rSub)
return getInfo(nums, 0, len(nums)-1).mSum