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

    文章

    4788

    浏览量

    68612
  • 数组
    +关注

    关注

    1

    文章

    417

    浏览量

    25947

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

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

收藏 人收藏

    评论

    相关推荐

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

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

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

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

    数组名之间可以直接赋值吗

    数组之间的赋值能不能直接使用等于号?比如这样的代码。 int main(){ int a[5] = {1, 2, 3, 4, 5}; int b[5] = {0}; b = a
    的头像 发表于 11-26 11:23 143次阅读

    指针数组和二维数组有没有区别

    指针数组和二维数组有没有区别?比如这样的两个代码。 int main(){ char *s1[] = { "hello", "world", "total" }; char s2[][6
    的头像 发表于 11-24 11:12 151次阅读

    C语言数组应用计算机导论A第6讲:数组

    C语言数组应用计算机导论A第6讲:数组
    发表于 11-20 15:33 0次下载

    labview按读取二维数组之后再按读取顺序重新组成二维数组如何实现?

    labview用了index Array按索引一行行读取二维数组之后想再按读取顺序重新组成一个二维数组如何实现,即第一次读取的作为第一,第二次读取的作为第二
    发表于 10-25 21:06

    labview字符串数组转化为数值数组

    在LabVIEW中,将字符串数组转换为数值数组是一项常见的任务,尤其是在处理数据采集、信号处理或用户输入时。 1. 理解LabVIEW的数据类型 在开始之前,了解LabVIEW中的数据类型是非
    的头像 发表于 09-04 17:47 2344次阅读

    面试常考+1:函数指针与指针函数、数组指针与指针数组

    在嵌入式开发领域,函数指针、指针函数、数组指针和指针数组是一些非常重要但又容易混淆的概念。理解它们的特性和应用场景,对于提升嵌入式程序的效率和质量至关重要。一、指针函数与函数指针指针函数:定义:指针
    的头像 发表于 08-10 08:11 861次阅读
    面试常考+1:函数指针与指针函数、<b class='flag-5'>数组</b>指针与指针<b class='flag-5'>数组</b>

    如何在代码动态修改手指电容?

    如何在代码动态修改手指电容
    发表于 05-22 07:11

    stm32通过串口发送字符串存在数组里面遇到的疑问求解

    如题,想通过串口发送字符串存在数组里面,然后在串口助手上打印出来,一开始计数值没有清零导致数据总是会被覆盖,调试了好久终于搞定,但是我在仿真窗口观察数组数据发现了问题,数据并没有存放在数组
    发表于 05-14 08:15

    深入探索KUKA KRL中的数组应用

    如果 CHAR 类型数组的所有数组元素都拥有相同的字符串,则不必单独初始化每个数组元素。忽略右侧的数组下标。(对于一维数组下标,不写下标。)
    的头像 发表于 04-18 10:37 1247次阅读
    深入探索KUKA KRL中的<b class='flag-5'>数组</b>应用

    PSoC4访问数组时产生无限循环的原因?

    我写了几乎 10000 代码,一切都工作正常,但最近我创建了一个 16 EVAL_2KW_48V_CHAR_P7的新数组,如果我访问它,程序就会挂起...... 调试器显示程序跳转到了不应该
    发表于 03-05 06:24

    数组和链表在内存中的区别 数组和链表的优缺点

    数组和链表在内存中的区别 数组和链表的优缺点  数组和链表是常见的数据结构,用于组织和存储数据。它们在内存中的存储方式以及优缺点方面存在一些显著的差异。本文将详细探讨这些差异以及它们的优缺点。 1.
    的头像 发表于 02-21 11:30 1037次阅读

    数组和链表有何区别

    数组和链表的区别,这个问题,不仅面试中经常遇到,考研的同学也得掌握才
    的头像 发表于 02-19 15:33 512次阅读
    <b class='flag-5'>数组</b>和链表有何区别

    PHP中数组的使用方法!

    PHP中数组的使用方法! PHP是一种广泛使用的网络编程语言,它的数组功能非常强大且灵活。数组是一种数据结构,它允许我们在单个变量中存储多个值。 在本篇文章中,我将详细解释PHP数组
    的头像 发表于 01-12 15:11 551次阅读