0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

动态规划:8行代码搞定最大子数组和问题

算法与数据结构 来源:码农的荒岛求生 作者:码农的荒岛求生 2022-04-01 10:24 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

铁子们,我是小风哥,你也可以叫我岛主

今天给大家带来一道极其经典的题目,叫做最大和子数组,给定一个数组,找到其中的一个连续子数组,其和最大。

示例:

输入:nums=[-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:子数组[4,-1,2,1]的和为6,其它任何连续的子数组和不超过6.

想一想该怎样解决这个问题。

如果你一时想不到解法可以从暴利解法开始。

暴力求解

这种解法最简单,我们把所有子数组找出来,然后依次计算其和,找出一个最大的出来,比如给定数组[1,2,3],那么我们能找出子数组:[1],[2],[3],[1,2],[2,3],[1,2,3],很显然这里和最大的子数组为[1,2,3],其值为6。

intsum(vector&nums,intb,inte){
intres=0;
for(;b<= e; b++) {
        res += nums[b];
    }
    returnres;
}
intmaxSubArray(vector&nums){
intsize=nums.size();
intres=0x80000000;
for(inti=0;i< size; i++) {
        for(intj=i;j< size; j++) {
            res = max(res, sum(nums, i, j));
        }
    }
    returnres;
}

这种解法最简单,该算法的时间复杂度为O(n^3),其中找出所有子数组的时间复杂度为O(n^2),计算每个子数组的和的时间复杂度为O(n),因此其时间复杂度为O(n^3)。

让我们再来看一下这个过程,这里的问题在于计算每个子数组的和时有很多重复计算,比如我们知道了子数组[1,2]的和后再计算数组[1,2,3]的值时完全可以利用子数组[1,2]的计算结果而无需从头到尾再算一遍,也就是说我们可以利用上一步的计算结果,这本身就是动态规划的思想。

8589ba58-b15c-11ec-aa7f-dac502259ad0.png

基于该思想我们可以对上述代码简单改造一下:

intmaxSubArray(vector&nums){
intsize=nums.size();
intres=0x80000000;
for(inti=0;i< size; i++) {
        int sumer = nums[i];
        res = max(res, sumer);
        for(intj=i+1;j< size; j++) {
            sumer += nums[j];
            res = max(res, sumer);
        }
    }
    returnres;
}

看到了吧,代码不但更简洁,而且运行速度更快,该算法的时间复杂度为O(n^2),比第一种解法高了很多。

还有没有进一步提高的空间呢?

答案是肯定的。

分而治之

我们在之前的文章中说过,分治也是一种非常强大的思想,具体应该这里的话我们可以把整个数组一分为二,然后子数组也一分为二,不断划分下去就像这样:

859b978c-b15c-11ec-aa7f-dac502259ad0.png

然后呢?

然后问题才真正开始有趣起来,注意,当我们划分到最下层的时候,也就是不可再划分时会得到两个数组元素,比如对于数组[1,2]会划分出[1]与[2],此时[1]与[2]不可再划分,那么对于子问题[1,2],其最大子数组的和为max(1+2, 1,2),也就是说要么是左半部分的元素值、要么是右半部分的元素值、要么是两个元素的和,就这样我们得到了最后两层的答案:

85bb2606-b15c-11ec-aa7f-dac502259ad0.png

假设对于数组[1,2,3,4],一次划分后得到了[1,2]与[3,4],用上面的方法我们可以分别知道这两个问题的最大子数组和,我们怎样利用上述的答案来解决更大的问题,也就是[1,2,3,4]呢?

很显然,对于[1,2,3,4]来说,最大子数组的和要么来自左半部分、要么来自右半部分、要么来自中间部分——也就是包含2和3,其中左半部分和右半部分的答案我们有了,那么中间部分的最大和该是多少呢?

其实这个问题很简单,我们从中间开始往两边不断累加,然后记下这个过程的最大值,比如对于[1,-2,3,-4,5],我们从中间的3开始先往左边累加和是:{1+(-2)+3, (-2)+3, 3}也就是{2,1,3},因此我们以中间数字为结尾的最大子数组和为3:

85ce2bde-b15c-11ec-aa7f-dac502259ad0.png

另一边也是同样的道理,只不过这次是以中间数字为起点向右累加:

85e0d090-b15c-11ec-aa7f-dac502259ad0.png

然后这三种情况中取一个最大值即可,这样我们就基于子问题解决了更大的问题:

85f977a8-b15c-11ec-aa7f-dac502259ad0.png

此后的道理一样,最终我们得到了整个问题的解。

根据上面的分析就可以写代码了:

intgetMaxSum(vector&nums,intb,inte){
if(b==e)returnnums[b];
if(b==e-1)returnmax(nums[b],max(nums[e],nums[b]+nums[e]));
intm=(b+e)/2;
intmaxleft=nums[m];
intmaxright=nums[m];
intsum=nums[m];

for(inti=m+1;i<= e; i++) {
        sum += nums[i];
        maxright = max(maxright, sum);
    }

    sum = nums[m];
    for(inti=m-1;i>=b;i--){
sum+=nums[i];
maxleft=max(maxleft,sum);
}
returnmax(getMaxSum(nums,b,m-1),max(getMaxSum(nums,m+1,e),maxleft+maxright-nums[m]));
}
intmaxSubArray(vector&nums){
returngetMaxSum(nums,0,nums.size()-1);
}

上述这段代码的时间复杂度为O(NlogN)比第二种方法又提高了很多。

动态规划

实际上这个问题还有另一种更妙的解决方法,我们令dp(i)表示以元素A[i]为结尾的最大子数组的和,那么根据这一定义则有:

860dadd6-b15c-11ec-aa7f-dac502259ad0.png

这是很显然的,注意dp(i)的定义,是以元素A[i]为结尾的最大子数组的和,因此dp(i)的值要么就是A[i]连接上之前的一个子数组,那么不链接任何数组,那么最终的结果一定是以某个元素为结尾的子数组,因此我们从所有的dp(i)中取一个最大的就好了,依赖子问题解决当前问题的解就是所谓的动态规划。

有了这些分析,代码非常简单:

intmaxSubArray(vector&nums){
intsize=nums.size();
vectordp(size,0);
intres=dp[0]=nums[0];
for(inti=1;i< size; i++) {
        dp[i] = max(dp[i - 1] + nums[i], nums[i]);
        res = max(res, dp[i]);
    }
    returnres;
}

这段代码简单到让人难以置信,只有8行代码,你甚至可能会怀疑这段代码的正确性,但它的确是没有任何问题的,而且这段代码的时间复杂度只有O(N),这段代码既简单运行速度又快,这大概就是算法的魅力吧。

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4941

    浏览量

    73135
  • 数组
    +关注

    关注

    1

    文章

    420

    浏览量

    27114

原文标题:动态规划:8行代码搞定最大子数组和问题

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    二维数组介绍

    大家不要认为二维数组在内存中就是按、列这样二维存储的,实际上,不管二维、三维数组… 都是编译器的语法糖。 存储上和一维数组没有本质区别,举个例子: int array[3][3
    发表于 11-25 07:42

    基于感知引导的多步骤精细操作任务与运动规划

    传统的任务与运动规划(TAMP)系统在机器人操作应用中通常依赖静态模型运行,因此在面对新环境时往往表现不佳。将感知与操作相融合,是应对这一挑战的有效途径,使机器人能够在执行过程中实时更新规划,从而适应动态变化的场景。
    的头像 发表于 11-14 10:18 1066次阅读
    基于感知引导的多步骤精细操作任务与运动<b class='flag-5'>规划</b>

    基于SIMP与折衷规划法的航空附件齿轮箱结构轻量化设计与动态特性提升

    航空发动机附件齿轮箱作为动力传递系统的关键部件,其箱体结构设计直接影响发动机的功率密度、可靠性及振动特性。针对传统经验设计方法难以满足高刚度、轻量化及高动态性能要求的挑战,本文提出了一种基于折衷规划法的多目标拓扑优化方法。
    的头像 发表于 11-07 15:21 421次阅读
    基于SIMP与折衷<b class='flag-5'>规划</b>法的航空附件齿轮箱结构轻量化设计与<b class='flag-5'>动态</b>特性提升

    商品价格动态调整接口技术详解

    ​  在电商或零售系统中,商品价格需根据市场动态(如供需变化、竞争环境)实时调整,以最大化利润和竞争力。本文将从接口设计、核心算法、实现代码到优化策略,逐步解析如何构建一个高效的“商品价格动态
    的头像 发表于 10-13 15:49 214次阅读
    商品价格<b class='flag-5'>动态</b>调整接口技术详解

    虹科动态 | 2025年8月精彩回顾

    」...下面让我们一起回顾8月的虹科动态吧,9月精彩活动预告也不容错过!01虹科动态1品牌动态8月7日,香港工业总会在港举办第65届周年大会
    的头像 发表于 09-02 10:13 657次阅读
    虹科<b class='flag-5'>动态</b> | 2025年<b class='flag-5'>8</b>月精彩回顾

    华砺智成立八周年

    以落地中国为起点,2017年8月,华砺智砥砺八载春秋,从几行通信代码到覆盖三大洲的智慧路网,用2920个昼夜完成了一场关于坚持的叙事,淬炼出坚固、立体、创新的多面形态。
    的头像 发表于 08-20 14:51 644次阅读

    AGV小车中的动态路径规划算法揭秘

    并非一成不变时,动态路径规划能力就显得至关重要。本文将深入探讨几种主流的动态路径规划算法(如A、Dijkstra、RRT等),并解析它们如何在AGV行业中大显身手。 为何需要
    的头像 发表于 06-17 15:54 1158次阅读
    AGV小车中的<b class='flag-5'>动态</b>路径<b class='flag-5'>规划</b>算法揭秘

    二维数组指定条件删除指定,请教

    数组1的第一列进行条件判断,如果小于20,删除所在行,最终需要得到数组2
    发表于 05-13 08:11

    业精于视,成于旗—视旗科技CEO曾帅先生专访

    企业家,用二十年生动诠释“学一爱一干一”的强大精神力量可以实现人生“逆袭”。相信才会看见,信仰力量和赤子情怀驱动他在上海9年、深圳8年奋发图强;看见会有未来,
    的头像 发表于 04-25 17:19 551次阅读
    业精于视,<b class='flag-5'>行</b>成于旗—视旗科技CEO曾帅先生专访

    关于stm32,u8g2菜单之间切换(三)用u8g2写一个菜单无限左右循环

    让菜单循环播放只要用到的函数 void rotateRight (uint8_t *arr[], int n);让数组右移 void rotateLeft ( uint8_t *arr[], int
    的头像 发表于 03-11 09:10 1102次阅读

    HarmonyOS NEXT 原生应用/元服务-ArkTS代码调试使用断点

    光标置于该行上并按Ctrl + F8(macOS为Command+F8)。 当您设置断点时,相应的代码旁边会出现一个红点,如图。 在设置的断点红点处,单击鼠标右键,在Conditio
    发表于 03-04 17:15

    8通道LED阳极驱动电路——AiP3318

    8通道LED阳极驱动电路——AiP3318
    的头像 发表于 02-18 09:49 876次阅读
    <b class='flag-5'>8</b>通道LED阳极<b class='flag-5'>行</b>驱动电路——AiP3318

    RK3576有多强?实测三屏八摄像头轻松搞定

    RK3576参数强劲RK3576是瑞芯微推出的一款高性能AIoT处理器,这款芯片以其卓越的计算能力、多屏幕支持、强大的视频编解码能力和高效的协处理器而闻名。三屏8摄像头轻松搞定米尔基于他们推出
    的头像 发表于 01-16 08:06 1178次阅读
    RK3576有多强?实测三屏八摄像头轻松<b class='flag-5'>搞定</b>

    Labivew 实现鼠标在数组中选中元素时,精准的显示所在位置的、列值方法

    在项目开发中,遇到一个布尔的二维数组输入控件,选中数组元素并索引出行列的操作,试过其他大佬的方法,不是特别精准,,尝试一下还可以,分享给需要的朋友 *附件:选中二维数组元素并索引所在的
    发表于 12-21 18:07

    数组的下标为什么可以是负数

    最近有同学发来这样一段代码,并提出一个问题,数组的下标为什么可以是负数?     #include int main(){ const char *s = "helloworld"; const
    的头像 发表于 12-20 11:18 856次阅读