动态规划<4>最大子序和(maximum-subarray)
本节为动态规划第四题:最大子序和(maximum-subarray),主要包括:问题描述,算法思路,复杂度分析,C++实现。
1. 问题描述
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
2. 解法
2.1 动态规划
这样不难写出一个时间和空间复杂度为O(n)实现,但是f(i)只与f(i-1)相关,所以我们可以只用一个变量pre来维护当前f(i)对应的f(i-1)的值,从而降低空间复杂度为O(1),C++代码如下:
//1. 动态规划解法,时间复杂度O(n),空间复杂度O(1)
int maxSubArray(vector<int>& nums) {
int pre = 0, maxAns = nums[0];
for (const auto& x : nums) {
pre = max(pre + x, x);
maxAns = max(maxAns, pre);
}
return maxAns;
}
2.2 分治法
定义一个操作get(a,l,r),表示查询a序列[l,r]区间内的最大字段和,那么我们要求的就是get(nums,0,nums.size()-1)。
用分治法实现这个操作,对于一个区间[l,r],m=下取整((l+r)/2),对区间[l,m]和[m+1,r]的分治求解,当递归逐层深入直到区间长度缩小为1的时候,递归开始回升,这个时候考虑如何通过两个区间的信息合并成区间[l,r]的信息,有两个关键点:
如何维护区间的那些信息
如何合并这些信息
对于一个区间[l,r],我们可以维护四个量:lSum:表示[l,r]内以l为左端点的最大子段和
rSum:表示[l,r]内以r为右端点的最大子段和
mSUm:表示[l,r]内的最大字段和
iSum:表示[l,r]的区间和
以下简称[l,m]为[l,r]的左子区间,对于长度大于1的区间:
首先最好维护的是iSum,区间[l,r]的iSum就等于左子区间的iSum加上右子区间的iSum
对于[l,r]的lSum,存在两种可能,要么等于左子区间的lSum,要么等于左子区间的iSum加上右子区间的lSum,二者取较大者
rSum同理要么等于右子区间的rSum,要么等于右子区间的iSum加上左子区间的rSum,二者取较大者
有了上面三个量[l,r]的mSum可能是左子区间的mSum和右子区间的mSum中的一个,也有可能是左子区间的rSum和右子区间的lSum求和,三者取大。
这样的方法时间复杂度相同,由于使用递归,空间复杂度更大,它存在的意义是这种方法不仅可以解决区间[0,n-1],它可以解决任意子区间的问题,如果我们把[0,n-1]分治下取出现的所有子区间的信息都用堆式存储的方式记忆化下来,建立成一颗真正的树,我们就可以在O(logn)时间找到任意区间的答案,甚至可以修改序列的值做一些简单的维护。对于大规模的查询问题,这种方法的优势便得以体现,这棵树也就是线段树。
C++代码:
//2. 分治法,时间复杂度O(n),空间复杂度O(logn)。
struct Status {
int lSum, rSum, mSum, iSum;
};
Status pushUp(Status l, Status r) {
int iSum = l.iSum + r.iSum;
int lSum = max(l.lSum, l.iSum + r.lSum);
int rSum = max(r.rSum, r.iSum + l.rSum);
int mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum);
return Status{ lSum, rSum, mSum, iSum };
}
Status get(vector<int>& a, int l, int r) {
if (l == r)
return Status{
a[1], a[1], a[1], a[1]
};
int m = (1 + r) >> 1;
Status lSub = get(a, 1, m);
Status rSub = get(a, m + 1, r);
return pushUp(lSub, rSub);
}
int maxSubArray(vector<int>& nums) {
return get(nums, 0, nums.size() - 1).mSum;
}
深度学习与数学
关注公号,发现更多
长按识别上方二维码关注