【动态规划】LeetCode 53. Maximum Subarray

LeetCode 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.

      在本篇文章中贴几个除了暴力求解外的方法,整理自博客http://www.cnblogs.com/waytofall/archive/2012/04/10/2439820.html和博客http://www.cnblogs.com/boring09/p/4252780.html

      方法一:动态规划

      贴一个知乎上关于动态规划的讨论链接https://www.zhihu.com/question/23995189

      令sum[i]是以第i个元素结尾且和最大的子串。对于数组元素a[i],以a[i]结尾且和最大的子串,要么是以第i-1个元素结尾且和最大(sum[i-1])的子串加a[i],要么是只包含a[i]元素,即sum[i] = max(sum[i-1] + a[i], a[i]),亦即等价于判断sum[i-1]是否大于0。

可以得到此题的代码如下:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
    int sum = nums[0];
    int maxSum = sum;
    for (int i=1;i<nums.size();i++) {
        sum = max(nums[i], sum + nums[i]);
        maxSum = max(maxSum, sum);
    }
     return maxSum;
    }
};

时间复杂度O(n),空间复杂度O(1)。

      方法二:扫描法

      个人感觉和动态规划一样只是理解方式不同而已。。后加注:这里提到的扫描法存在一个问题就是如果最大字段和小于0则算法没法给出正确答案。其实这个问题用动态规划就好,这里的扫描法其实真的不是个好方法,只是因为很有名所以还是粘出来了。
      当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新清零,不然的话这个负数将会减少接下来的和。实现:
//copyright@ July 2010/10/18  
//updated,2011.05.25.  
#include <iostream.h>  
  
int maxSum(int* a, int n)  
{  
    int sum=0;  
    //其实要处理全是负数的情况,很简单,如稍后下面第3点所见,直接把这句改成:"int sum=a[0]"即可  
    //也可以不改,当全是负数的情况,直接返回0,也不见得不行。  
    int b=0;  
      
    for(int i=0; i<n; i++)  
    {  
        if(b<0)           //...  
            b=a[i];  
        else  
            b+=a[i];  
        if(sum<b)  
            sum=b;  
    }  
    return sum;  
}  
  
int main()  
{  
    int a[10]={1, -2, 3, 10, -4, 7, 2, -5};  
    //int a[]={-1,-2,-3,-4};  //测试全是负数的用例  
    cout<<maxSum(a,8)<<endl;  
    return 0;  
}  
  
/*------------------------------------- 
解释下: 
例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5, 
那么最大的子数组为3, 10, -4, 7, 2, 
因此输出为该子数组的和18。 
 
所有的东西都在以下俩行, 
即: 
b  :  0  1  -1  3  13   9  16  18  13   
sum:  0  1   1  3  13  13  16  18  18 
   
其实算法很简单,当前面的几个数,加起来后,b<0后, 
把b重新赋值,置为下一个元素,b=a[i]。 
当b>sum,则更新sum=b; 
若b<sum,则sum保持原值,不更新。。July、10/31。

      时间复杂度O(n),空间复杂度O(1)

      方法三:分而治之

假设求A[l..r]的最大子串和

首先将其分成两半A[l..m]和A[m+1..r],其中m=(l+r)/2,并分别求递归求出这两半的最大子串和,不妨称为left,right。如下图所示:

A[l..r]的连续子串和可能出现在左半边(即left),或者可能出现在右半边(即right),还可能出现在横跨左右两半的地方(即middle),如下图橙色部分所示:

当然,middle完全有可能覆盖left或right,它可能的范围入下图所示:

那么,如何求middle?貌似没有什么简单的方法,只能从中间向两遍扫,也就是把上图种的范围扫一遍。具体怎么扫呢?见方法I和方法II

是不是突然觉得很坑爹?既然知道最后求middle要扫一遍,还不如一开始就从l到r扫一遍求max得了,还费什么劲儿求left和right呢?求left和right的作用仅限于缩小扫描的范围。

int diveNConquer(int A[], int l, int r) {
  if (l == r)
    return A[l];

  int m = (l + r) / 2;
  int left = diveNConquer(A, l, m);
  int right = diveNConquer(A, m + 1, r);
  int middle = A[m];
  for (int i = m - 1, tmp = middle; i >= l; i--) {
    tmp += A[i];
    middle = max(middle, tmp);
  }
  for (int i = m + 1, tmp = middle; i <= r; i++) {
    tmp += A[i];
    middle = max(middle, tmp);
  }

  return max(middle, max(left, right));
}

int maxSubArray(int A[], int n) {
  return diveNConquer(A, 0, n - 1);
}
      分析一下时间复杂度,设问题的工作量是T(n),则有T(n) = 2T(n/2) + O(n),解得T(n) = O(nlogn)。看看,效率反而低了不少。














  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值