53. Maximum Subarray

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;
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值