程序员编程艺术:第七章、求连续子数组的最大和

简介:

前奏

  • 希望更多的人能和我一样,把本狂想曲系列中的任何一道面试题当做一道简单的编程题或一个实质性的问题来看待,在阅读本狂想曲系列的过程中,希望你能尽量暂时放下所有有关面试的一切包袱,潜心攻克每一道“编程题”,在解决编程题的过程中,好好享受编程带来的无限乐趣,与思考带来的无限激情。--By@July_____
  • 原狂想曲系列已更名为:程序员编程艺术系列。原狂想曲创作组更名为编程艺术室。编程艺术室致力于以下三点工作:1、针对一个问题,不断寻找更高效的算法,并予以编程实现。2、解决实际中会碰到的应用问题,如第十章、如何给10^7个数据量的磁盘文件排序。3、经典算法的研究与实现。总体突出一点:编程,如何高效的编程解决实际问题。欢迎有志者加入。


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

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

分析:这个问题在各大公司面试中出现频率之频繁,被人引用次数之多,非一般面试题可与之匹敌。单凭这点,就没有理由不入选狂想曲系列中了。此题曾作为本人之前整理的微软100题中的第3题,至今反响也很大。ok,下面,咱们来一步一步分析这个题:
      1、求一个数组的最大子数组和,如此序列1, -2, 3, 10, -4, 7, 2, -5,我想最最直观也是最野蛮的办法便是,三个for循环三层遍历,求出数组中每一个子数组的和,最终求出这些子数组的最大的一个值。
记Sum[i, …, j]为数组A中第i个元素到第j个元素的和(其中0 <= i <= j < n),遍历所有可能的Sum[i, …, j],那么时间复杂度为O(N^3):

//本段代码引自编程之美
int MaxSum(int* A, int n)
{
 int maximum = -INF; 
 int sum=0;   
 for(int i = 0; i < n; i++)
 {
  for(int j = i; j < n; j++)
  {
   for(int k = i; k <= j; k++)
   {
    sum += A[k];
   }
   if(sum > maximum)
    maximum = sum;

   sum=0;   //这里要记得清零,否则的话sum最终存放的是所有子数组的和。也就是编程之美上所说的bug。多谢苍狼。
  }
 }
 return maximum;

      2、其实这个问题,在我之前上传的微软100题,答案V0.2版[第1-20题答案],便直接给出了以下O(N)的算法:

  1. //copyright@ July 2010/10/18  
  2. //updated,2011.05.25.  
  3. #include <iostream.h>  
  4.   
  5. int maxSum(int* a, int n)  
  6. {  
  7.     int sum=0;  
  8.     //其实要处理全是负数的情况,很简单,如稍后下面第3点所见,直接把这句改成:"int sum=a[0]"即可  
  9.     //也可以不改,当全是负数的情况,直接返回0,也不见得不行。  
  10.     int b=0;  
  11.       
  12.     for(int i=0; i<n; i++)  
  13.     {  
  14.         if(b<0)           //...  
  15.             b=a[i];  
  16.         else  
  17.             b+=a[i];  
  18.         if(sum<b)  
  19.             sum=b;  
  20.     }  
  21.     return sum;  
  22. }  
  23.   
  24. int main()  
  25. {  
  26.     int a[10]={1, -2, 3, 10, -4, 7, 2, -5};  
  27.     //int a[]={-1,-2,-3,-4};  //测试全是负数的用例  
  28.     cout<<maxSum(a,8)<<endl;  
  29.     return 0;  
  30. }  
  31.   
  32. /*------------------------------------- 
  33. 解释下: 
  34. 例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5, 
  35. 那么最大的子数组为3, 10, -4, 7, 2, 
  36. 因此输出为该子数组的和18。 
  37.  
  38. 所有的东西都在以下俩行, 
  39. 即: 
  40. b  :  0  1  -1  3  13   9  16  18  13   
  41. sum:  0  1   1  3  13  13  16  18  18 
  42.    
  43. 其实算法很简单,当前面的几个数,加起来后,b<0后, 
  44. 把b重新赋值,置为下一个元素,b=a[i]。 
  45. 当b>sum,则更新sum=b; 
  46. 若b<sum,则sum保持原值,不更新。。July、10/31。 
  47. ----------------------------------*/  

 

      3、不少朋友看到上面的答案之后,认为上述思路2的代码,没有处理全是负数的情况,当全是负数的情况时,我们可以让程序返回0,也可以让其返回最大的那个负数,下面便是前几日重写的,修改后的处理全是负数情况(返回最大的负数)的代码:

 

  1. //copyright@ July  
  2. //July、updated,2011.05.25。  
  3. #include <iostream.h>  
  4. #define n 4           //多定义了一个变量  
  5.   
  6. int maxsum(int a[n])    
  7. //于此处,你能看到上述思路2代码(指针)的优势  
  8. {  
  9.     int max=a[0];       //全负情况,返回最大数  
  10.     int sum=0;  
  11.     for(int j=0;j<n;j++)  
  12.     {  
  13.         if(sum>=0)     //如果加上某个元素,sum>=0的话,就加  
  14.             sum+=a[j];  
  15.         else     
  16.             sum=a[j];  //如果加上某个元素,sum<0了,就不加  
  17.         if(sum>max)  
  18.             max=sum;  
  19.     }  
  20.     return max;  
  21. }  
  22.   
  23. int main()  
  24. {  
  25.     int a[]={-1,-2,-3,-4};  
  26.     cout<<maxsum(a)<<endl;  
  27.     return 0;  
  28. }  

 

      4、DP解法的具体方程:@ flyinghearts:设sum[i] 为前i个元素中,包含第i个元素且和最大的连续子数组,result 为已找到的子数组中和最大的。对第i+1个元素有两种选择:做为新子数组的第一个元素、放入前面找到的子数组。
sum[i+1] = max(a[i+1], sum[i] + a[i+1])
result = max(result, sum[i])
 

扩展:
1、如果数组是二维数组,同样要你求最大子数组的和列?
2、如果是要你求子数组的最大乘积列?
3、如果同时要求输出子段的开始和结束列?

 

第二节、Data structures and Algorithm analysis in C

下面给出《Data structures and Algorithm analysis in C》中4种实现。

 

  1. //感谢网友firo  
  2. //July、2010.06.05。  
  3.   
  4. //Algorithm 1:时间效率为O(n*n*n)  
  5. int MaxSubsequenceSum1(const int A[],int N)  
  6. {  
  7.     int ThisSum=0 ,MaxSum=0,i,j,k;  
  8.     for(i=0;i<N;i++)  
  9.         for(j=i;j<N;j++)  
  10.         {  
  11.             ThisSum=0;  
  12.             for(k=i;k<j;k++)  
  13.                 ThisSum+=A[k];  
  14.               
  15.             if(ThisSum>MaxSum)  
  16.                 MaxSum=ThisSum;  
  17.         }  
  18.         return MaxSum;  
  19. }  
  20.   
  21. //Algorithm 2:时间效率为O(n*n)  
  22. int MaxSubsequenceSum2(const int A[],int N)  
  23. {  
  24.     int ThisSum=0,MaxSum=0,i,j,k;  
  25.     for(i=0;i<N;i++)  
  26.     {  
  27.         ThisSum=0;  
  28.         for(j=i;j<N;j++)  
  29.         {  
  30.             ThisSum+=A[j];  
  31.             if(ThisSum>MaxSum)  
  32.                 MaxSum=ThisSum;  
  33.         }  
  34.     }  
  35.     return MaxSum;  
  36. }  
  37.   
  38. //Algorithm 3:时间效率为O(n*log n)  
  39. //算法3的主要思想:采用二分策略,将序列分成左右两份。  
  40. //那么最长子序列有三种可能出现的情况,即  
  41. //【1】只出现在左部分.  
  42. //【2】只出现在右部分。  
  43. //【3】出现在中间,同时涉及到左右两部分。  
  44. //分情况讨论之。  
  45. static int MaxSubSum(const int A[],int Left,int Right)  
  46. {  
  47.     int MaxLeftSum,MaxRightSum;              //左、右部分最大连续子序列值。对应情况【1】、【2】  
  48.     int MaxLeftBorderSum,MaxRightBorderSum;  //从中间分别到左右两侧的最大连续子序列值,对应case【3】。  
  49.     int LeftBorderSum,RightBorderSum;  
  50.     int Center,i;  
  51.     if(Left == Right)Base Case  
  52.         if(A[Left]>0)  
  53.             return A[Left];  
  54.         else  
  55.             return 0;  
  56.         Center=(Left+Right)/2;  
  57.         MaxLeftSum=MaxSubSum(A,Left,Center);  
  58.         MaxRightSum=MaxSubSum(A,Center+1,Right);  
  59.         MaxLeftBorderSum=0;  
  60.         LeftBorderSum=0;  
  61.         for(i=Center;i>=Left;i--)  
  62.         {  
  63.             LeftBorderSum+=A[i];  
  64.             if(LeftBorderSum>MaxLeftBorderSum)  
  65.                 MaxLeftBorderSum=LeftBorderSum;  
  66.         }  
  67.         MaxRightBorderSum=0;  
  68.         RightBorderSum=0;  
  69.         for(i=Center+1;i<=Right;i++)  
  70.         {  
  71.             RightBorderSum+=A[i];  
  72.             if(RightBorderSum>MaxRightBorderSum)  
  73.                 MaxRightBorderSum=RightBorderSum;  
  74.         }  
  75.         int max1=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;  
  76.         int max2=MaxLeftBorderSum+MaxRightBorderSum;  
  77.         return max1>max2?max1:max2;  
  78. }  
  79.   
  80. //Algorithm 4:时间效率为O(n)  
  81. //同上述第一节中的思路3、和4。  
  82. int MaxSubsequenceSum(const int A[],int N)  
  83. {  
  84.     int ThisSum,MaxSum,j;  
  85.     ThisSum=MaxSum=0;  
  86.     for(j=0;j<N;j++)  
  87.     {  
  88.         ThisSum+=A[j];  
  89.         if(ThisSum>MaxSum)  
  90.             MaxSum=ThisSum;  
  91.         else if(ThisSum<0)  
  92.             ThisSum=0;  
  93.     }  
  94.     return MaxSum;  
  95. }   

本文转自博客园知识天地的博客,原文链接:程序员编程艺术:第七章、求连续子数组的最大和,如需转载请自行联系原博主。

相关文章
|
3月前
|
人工智能 数据可视化 测试技术
UAT测试排程工具深度解析:让验收测试不再失控,项目稳稳上线
在系统交付节奏加快的背景下,“测试节奏混乱”已成为项目延期的主因之一。UAT测试排程工具应运而生,帮助团队结构化拆解任务、清晰分配责任、实时掌控进度,打通需求、测试、开发三方协作闭环,提升测试效率与质量。本文还盘点了2025年热门UAT工具,助力团队选型落地,告别靠表格和群聊推进测试的低效方式,实现有节奏、有章法的测试管理。
|
10月前
|
机器学习/深度学习 人工智能 自然语言处理
Genesis:卡内基梅隆大学联合 20 多所研究机构开源生成式物理引擎,能够模拟各种材料、物体和物理运动现象
Genesis是由卡内基梅隆大学联合20多所研究机构开源的生成式物理引擎,能够模拟世界万物,具有高度的物理准确性和快速的模拟速度,适用于机器人仿真、游戏开发、电影特效制作等多个领域。
462 21
Genesis:卡内基梅隆大学联合 20 多所研究机构开源生成式物理引擎,能够模拟各种材料、物体和物理运动现象
|
7月前
|
运维 安全 持续交付
Websoft9 运维面板有多好用,试了才知道
Websoft9 运维面板以开源应用全生命周期管理为核心,内置 ​200+ 应用模板,支持 WordPress、Magento 等一键部署,10 分钟完成复杂环境搭建,对比宝塔节省 80% 手动配置时间。其基于 ​GitOps 架构,实现代码与数据分离管理,支持持续部署与回滚。安全层面提供全链路防护,覆盖漏洞扫描、WAF 防御及一键修复,资源占用仅为 cPanel 的 1/3,适配多云及 Docker/K8s 生态。相较于宝塔,Websoft9 在开源适配深度、自动化能力及轻量化设计上优势显著,尤其适合企业级 SaaS 与开发者。
221 0
|
12月前
|
机器学习/深度学习 人工智能 算法框架/工具
基于人体姿势估计的舞蹈检测(AI Dance based on Human Pose Estimation)
基于人体姿势估计的舞蹈检测(AI Dance based on Human Pose Estimation)
303 0
|
消息中间件 负载均衡 Kafka
MQ消息路由大揭秘!从菜鸟到高手,一文带你玩转消息传递的‘高速公路’,轻松实现订单秒级响应!
【8月更文挑战第24天】在现代分布式系统中,消息队列(MQ)作为系统间解耦的核心工具,支持异步处理、负载均衡及高可用性。消息路由是MQ中的关键环节,决定消息从生产者到消费者的路径。主流MQ产品如RabbitMQ、Kafka等采用相似的路由机制,涉及交换器、队列、路由键等概念。常见的路由模式包括直接交换、主题交换及发布/订阅模式。以RabbitMQ为例,通过直接交换模式,可以根据订单类型(如“普通订单”、“紧急订单”)将消息路由至相应的处理队列。这一过程展示了MQ系统如何基于路由键和队列绑定关系实现消息的有效传递。
358 2
|
前端开发 JavaScript Python
flask实战-模板实现公共导航
在Flask中实现模板继承,创建基础模板`base.html`,包含公共导航菜单。子模板`movie-extends.html`继承`base.html`,并定义主要内容。视图函数`movie_extends_view`渲染`movie-extends.html`,显示电影列表。通过`extra_css`和`extra_js`块添加页面特定的样式和脚本,实现在`movie-extends.html`中应用自定义CSS样式。运行应用,访问http://127.0.0.1:1027/movie-extends,页面显示定制的电影列表样式。
238 2
|
机器学习/深度学习 算法 数据挖掘
Python数据分析革命:Scikit-learn库,让机器学习模型训练与评估变得简单高效!
【7月更文挑战第27天】在数据驱动时代,Python以丰富的库成为数据科学首选。Scikit-learn因简洁高效而备受青睐,引领数据分析革命。本文引导您使用Scikit-learn简化机器学习流程。首先通过`pip install scikit-learn`安装库。接着使用内置数据集简化数据准备步骤,例如加载Iris数据集。选择合适的模型,如逻辑回归,并初始化与训练模型。利用交叉验证评估模型性能,获取准确率等指标。最后,应用训练好的模型进行新数据预测。Scikit-learn为各阶段提供一站式支持,助力数据分析项目成功。
213 0
|
运维 网络协议 安全
Linux vsFTPd服务详解——vsFTPd基础知识
Linux vsFTPd服务详解——vsFTPd基础知识
259 4
|
数据挖掘
大学物理实验-居家实验1-利用单摆测量重力加速度
大学物理实验-居家实验1-利用单摆测量重力加速度
603 0
大学物理实验-居家实验1-利用单摆测量重力加速度
|
Java
【Java】获取中文首字母
获取中文首字母 有时候,比如微信上根据名字拼音首字母排序并检索。那么用Java获取中文首字母是怎么实现的呢? 代码 不多说,直接上代码:(写个工具类) public class FirstCharUtil { // 简体中文的编码范围从B0A1...
4598 0