1、动态规划法
在使用动态规划法解决问题时,我们需要确保子问题之间是没有不确定的地方的,即需要确保子问题之间是无后效性的。无后效性实际上是这样的一个定义:
为了保证计算子问题能够按照顺序、不重复地进行,动态规划要求已经求解的子问题不受后续阶段的影响。这个条件也被叫做「无后效性」。换言之,动态规划对状态空间的遍历构成一张有向无环图,遍历就是该有向无环图的一个拓扑序。有向无环图中的节点对应问题中的「状态」,图中的边则对应状态之间的「转移」,转移的选取就是动态规划中的「决策」。
在确定子问题时,最简单的想法是将dp数组定义为前i位中的最大连续子数组,这样子我们只需要遍历一遍数组,则最后的dp值就是我们需要求的最大子数组和。但这样子分割子问题存在如下问题:当i移动的时候,我们可能出现多种情况,可能最大连续子数组不变,可能需要加上最新的nums[i],可能只有最新的nums[i],也可能需要从原先的最大连续子数组中删除掉一些数,这样导致子问题之间的关系不够清晰。
为了克服上述缺点,我们可以将dp数组定义成以nums[i]作为结尾的连续子数组的最大和是多少。这样子做的好处有如下几点:1、子问题之间存在的关联较小,我们只需要比较dp[i-1]+nums[i]和nums[i]的值那一个更大即可,避免了对于过多关系的讨论;2、最终在求解最大和时,我们只需要再遍历一遍dp数组即可返回最大值,时间复杂度仍然是O(n)。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size(), 0);
int result = INT_MIN;
dp[0] = nums[0];
for (int i = 1; i < nums.size(); ++i) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
}
for (auto i: dp) {
result = max(result, i);
}
return result;
}
};
或
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size(), 0);
int result = nums[0];
dp[0] = nums[0];
for (int i = 1; i < nums.size(); ++i) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
result = max(result, dp[i]);
}
return result;
}
};
其中第二份代码在一次循环中完成了对dp的设置以及对dp的比较,进一步降低时间复杂度。
2、分治法
使用分治法解决问题时我们同样需要将问题分解成多个子问题,区别于动态规划法,分治法解决问题时子问题之间通常是递归的关系,我们通过求解至子问题而后回退至上一层,一步步求得初始结果。
我们可以定义get(a,l,r)表示查询序列a的[l,r]区间内的最大连续子数组的和,因而最终的目标是求解get(nums,o,nums.size()-1)。为了使用分治法,我们可以考虑将区间[l,r]分为两段,假设我们取m=(l+r)/2,则两段分别为[l,m],[m+1,r]。当每一段的长度为1时就是我们的子问题,而后我们需要将左右两段合并返回上一层的结果。其中最关键的在于:1、我们维护什么信息;2、我们怎么合并信息。
我们定义一个结构体内有四个变量:1、lSum 表示 [l,r]内以 l 为左端点的最大子段和;2、rSum 表示 [l,r] 内以 r 为右端点的最大子段和;3、mSum 表示 [l,r] 内的最大子段和;4、iSum 表示 [l,r] 的区间和。显然,当子段长度为1时,四个值都是相同的值,即nums[i]。当我们需要对子段进行合并时,我们需要考虑如下的方式进行合并:1、合并iSum,我们只需要将左右子区间的iSum相加即可;2、合并lSum,我们需要取原先左子区间的lSum和左子区间的iSum加上右子区间的lSum中的最大值;3、合并rSum,我们需要取原先右子区间的rSum和右子区间的iSum加上左子区间的rSum中的最大值;4、合并mSum,我们需要对两种情况进行讨论:一是该连续子数组没有跨越m,此时取左子区间的mSum 和右子区间的mSum 的较大值;二是该连续子数组跨越m,此时取左子区间的rSum 和右子区间的lSum 之和,三者取最大值即可。
class Solution {
public:
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[l], a[l], a[l], a[l]};
}
int m = (l + r) / 2;
Status lSub = get(a, l, 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;
}
};