求连续子数组的最大和

来源:互联网 发布:python统计字母个数 编辑:程序博客网 时间:2024/05/01 08:45

求连续子数组的最大和

整理自 http://blog.csdn.net/v_JULY_v/article/details/6444021

求子数组的最大和
题目描述:

输入一个整形数组,数组里有正数也有负数。
数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
求所有子数组的和的最大值。要求时间复杂度为O(n)。

例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,
因此输出为该子数组的和18。

思路

一. 动态规划

设sum[i]为以第i个元素结尾且和最大的连续子数组。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i] = max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。

伪代码如下

复制代码
result = a[1]sum = a[1]for i: 2 to LENGTH[a]  if sum > 0    sum += a[i]  else    sum = a[i]  if sum > result    result = sumreturn result
复制代码

二. 扫描法

(后加注:这里提到的扫描法存在一个问题就是如果最大字段和小于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)。书中说,这个算法是一个统计学家提出的。

这个算法如此精炼简单,而且复杂度只有线性。但是我想,能想出来却非常困难,而且证明也不简单。在这里,我斗胆写出自己证明的想法:

关于这道题的证明,我的思路是去证明这样的扫描法包含了所有n^2种情况,即所有未显示列出的子数组都可以在本题的扫描过程中被抛弃。

1 首先,假设算法扫描到某个地方时,始终未出现加和小于等于0的情况。

我们可以把所有子数组(实际上为当前扫描过的元素所组成的子数组)列为三种:

1.1 以开头元素为开头,结尾为任一的子数组

1.2 以结尾元素为结尾,开头为任一的子数组

1.3 开头和结尾都不等于当前开头结尾的所有子数组

1.1由于遍历过程中已经扫描,所以算法已经考虑了。1.2确实没考虑,但我们随便找到1.2中的某一个数组,可知,从开头元素到这个1.2中的数组的加和大于0(因为如果小于0就说明扫描过程中遇到小于0的情况,不包括在大前提1之内),那么这个和一定小于从开头到这个1.2数组结尾的和。故此种情况可舍弃

1.3 可以以1.2同样的方法证明,因为我们的结尾已经列举了所有的情况,那么每一种情况和1.2是相同的,故也可以舍弃。

2 如果当前加和出现小于等于0的情况,且是第一次出现,可知前面所有的情况加和都不为0

一个很直观的结论是,如果子段和小于0,我们可以抛弃,但问题是是不是他的所有以此子段结尾为结尾而开头任意的子段也需要抛弃呢?

答案是肯定的。因为以此子段开头为开头而结尾任意的子段加和都大于0(情况2的前提),所以这些子段的和是小于当前子段的,也就是小于0的,对于后面也是需要抛弃的。也就是说,所有以之前的所有元素为开头而以当前结尾之后元素为结尾的数组都可以抛弃了。

而对于后面抛弃后的数组,则可以同样递归地用1 2两个大情况进行分析,于是得证。

这个算法的证明有些复杂,现在感觉应该不会错,至少思路是对的,谁帮着在表达上优化下吧。:-)

三. 分治,合并的时候穷举

(这个也是直接从原帖抄的,思路应该不难理解。鉴于复杂度高又递归,实际解决问题时不建议采用)

复制代码
//Algorithm 3:时间效率为O(n*log n)  //算法3的主要思想:采用二分策略,将序列分成左右两份。  //那么最长子序列有三种可能出现的情况,即  //【1】只出现在左部分.  //【2】只出现在右部分。  //【3】出现在中间,同时涉及到左右两部分。  //分情况讨论之。  static int MaxSubSum(const int A[],int Left,int Right)  {      int MaxLeftSum,MaxRightSum;              //左、右部分最大连续子序列值。对应情况【1】、【2】      int MaxLeftBorderSum,MaxRightBorderSum;  //从中间分别到左右两侧的最大连续子序列值,对应case【3】。      int LeftBorderSum,RightBorderSum;      int Center,i;      if(Left == Right)Base Case          if(A[Left]>0)              return A[Left];          else              return 0;          Center=(Left+Right)/2;          MaxLeftSum=MaxSubSum(A,Left,Center);          MaxRightSum=MaxSubSum(A,Center+1,Right);          MaxLeftBorderSum=0;          LeftBorderSum=0;          for(i=Center;i>=Left;i--)          {              LeftBorderSum+=A[i];              if(LeftBorderSum>MaxLeftBorderSum)                  MaxLeftBorderSum=LeftBorderSum;          }          MaxRightBorderSum=0;          RightBorderSum=0;          for(i=Center+1;i<=Right;i++)          {              RightBorderSum+=A[i];              if(RightBorderSum>MaxRightBorderSum)                  MaxRightBorderSum=RightBorderSum;          }          int max1=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;          int max2=MaxLeftBorderSum+MaxRightBorderSum;          return max1>max2?max1:max2;  }  
复制代码
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 老公不肯把钱交给老婆管怎么办 愿意和做朋友不愿意做情侣怎么办 欠信用卡碰到第三方不愿协调怎么办 qq密码忘记了申诉不回来怎么办 当你老公烦你了你该怎么办 自己有漂亮媳妇还经常想去嫖怎么办 微信号封了找不到好友解封怎么办 别人总是提起你的黑历史怎么办 换了手机微信登录不上怎么办 qq不小心清空了聊天记录怎么办 人家介绍了外地媳妇跑了怎么办 火锅想吃香菜牛肉没有签子串怎么办 想读外省大学但家人反对怎么办 13岁被同学忽视他不知道怎么办 老婆发现老公在微信暧昧聊天怎么办 老婆微信和别人聊天暧昧我该怎么办 淘宝修改标题宝贝被删了怎么办 百度网盘上的相片变的模糊怎么办 微信2不小心删了怎么办 房屋没交接前给钥匙出现问题怎么办 微信银行卡注销零钱没了怎么办 贴小广告电话被城管上报停机怎么办 没有id密码怎么办已经锁死了屏幕 电信电话卡注销了里面的钱怎么办 支付宝绑定的手机号成空号了怎么办 支付宝绑定的手机号空号了怎么办 支付宝绑定的手机号码空号了怎么办 注册支付宝的手机号成空号了怎么办 银行保本理财回执单丢了怎么办 电脑连不上网ip地址错误怎么办 邮箱填错了没收到面试通知怎么办 电信烽火网络机顶盒零配置中怎么办 专技天下学错科目了怎么办 注册公司行业大类选错了怎么办 私营企业开的车比领导好怎么办 有公司有商标无生产资质怎么办 天猫店铺使用商标被注销怎么办 入仓件快递员搞错入仓号怎么办 商标注册证盖了自己公司的章怎么办 普通机打发票作废票丢了怎么办 淘宝店铺被买家投诉工商介入怎么办