Find the contiguous(邻近的) subarray within an array (containing at least one number) which has the largest sum.
For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
the contiguous subarray [4,-1,2,1] has the largest sum = 6.
click to show more practice.
More practice:
If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.
方法一:暴力枚举,结果提交测试超时,复杂度O(n^3),too young too simple!
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int currSum=0;
int maxSum=nums[0]; //建立数组,用来保存所有子数组的和
for(int i=0;i<nums.size();i++)
{
for(int j=i;j<nums.size();j++)
{
for(int k=i;k<=j;k++) //currSum[i, …, j]为数组A中第i个元素到第j个元素的和(其中0 <= i <= j < n)
{
currSum +=nums[k];
}
if(currSum>maxSum)
maxSum=currSum;
currSum=0;//currSum清零,否则maxSum中保存的就是所有子数组的和
}
}
return maxSum;
}
};
方法二:动态规划
DP概述
动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。“”简单地说,问题能够分解成子问题来解决。“
”
令currSum是以当前元素结尾的最大子数组和,maxSum是全局的最大子数组和,当往后扫描时,对第j个元素有两种选择:要么放入前面找到的子数组,要么做为新子数组的第一个元素:如果currSum > 0,则令currSum加上a[j],反之,currSum < 0 时,则currSum 被置为当前元素,即currSum = nums[j]。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int currSum=0;
int maxSum=nums[0];
int len=nums.size();
for(int i=0;i<len;i++)
{
if(currSum>=0)
currSum +=nums[i];
else
currSum=nums[i];//currSum的最后一个元素必为nums[i],
//currSum=nums[i]+max(currSum,0);
if(currSum>maxSum)
maxSum=currSum;
}
return maxSum;
}
};
方法三:分治法(divide and conquer approach)
https://www.tianmaying.com/tutorial/LC53
首先,我们需要将问题抽象化一下,即如果我们用n表示序列的长度,用f(l, r)表示区间左右端点均在[l, r]范围内的所有连续子序列中序列和的最大值,那么我们要求的答案其实就是f(0, n - 1)。
那么如何求解f(l, r)呢?像之前所说,我们使用分治,即将一个大问题分解成若干小问题的方法来解决!
不妨设 m = (l + r) / 2,即区间的中心点,那么对于这个区间中和最大的子区间来说,只存在两种可能:要么这个子区间穿过了m,要么没有穿过。
如果穿过了m的话,我们就只需要找到左半部分区间中从m开始向左“区间和”最大的区间,和右半部分区间中从m开始向右“区间和”最大的区间,然后将这两个区间拼起来,就可以得到这个子区间(可以这样寻找这两个区间,即令sum[i] = nums[0] + nums[1] + … + nums[i],即nums的前缀和,这样就只需要找sum[l .. m]中的最小值,和sum[m + 1 .. r]中的最大值即可)(两者之差即可求出区间和)。
如果没有穿过m的话,那么一定完全处于左半部分区间中,或者完全处于右半部分区间中,我们只需要计算f(l, m)和f(m + 1, r)的较大值即可。
对于这样的两种方案,我们依次求出满足其设定的“最大”区间,然后进行比较,选出其中“最大”的区间即可。
由于问题的规模每次缩小一半,不难发现规模为n的问题有1个,规模为n/2的问题有2个,规模为n/4的问题有4个……,依次类推,我们可以知道最后问题的复杂度为O(nlogn),这样这道问题就可以得到完美的解决!
class Solution {
public:
int maxSubArray(vector<int>& nums) {
// 计算nums的前缀和
for (int i = 1; i < nums.size(); i++) {
nums[i] = nums[i - 1] + nums[i];
}
// 分治的去计算答案
int smin, smax;
return calc_max(nums, 0, nums.size() - 1, smin, smax);
}
// 抽象问题:区间范围在[l, r]内的最大区间是多少
int calc_max(vector<int> &nums, int l, int r, int &smin, int &smax) {
if (l == r) {
// 边界情况
smin = smax = nums[l];
return nums[l];
}
else {
int lmin, lmax, rmin, rmax, tmp, m = (l + r) / 2;
// 第一种可能,答案完全处于左半部分
int ans = calc_max(nums, l, m, lmin, lmax);
// 第二种可能,答案完全处于右半部分
ans = max(ans, calc_max(nums, m + 1, r, rmin, rmax));
// 第三种可能,答案横跨了两个部分
ans = max(ans, rmax - lmin); //前缀和之差得到子区间和
// 计算当前区间中的最大值和最小值
smin = min(lmin, rmin);
smax = max(lmax, rmax);
// 返回最大值
return ans;
}
}
};