码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • leetCode 53.最大子数和 图解 + 贪心算法/动态规划+优化


    53. 最大子数组和 - 力扣(LeetCode)


    给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。


    示例 1:

    输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
    输出:6
    解释:连续子数组 [4,-1,2,1] 的和最大,为 6 
    

    示例 2:

    输入:nums = [1]
    输出:1
    

    示例 3:

    输入:nums = [5,4,-1,7,8]
    输出:23

    >>思路和分析 

    一、贪心解法 贪心贪在哪里(=@__@=)?

    我们看示例1,若-2 和 1在一起累加,计算起点一定从1开始,因为负数只会拉低总和,这就是贪心贪的地方!

    • 局部最优:当前 “连续和” 为负数的时候立刻放弃,从下一个元素重新计算 “连续和”,因为负数加上下一个元素 “连续和” 只会越来越小。
    • 全局最优:选取最大“连续和”

    局部最优的情况下,并记录最大的 “连续和” ,可以推出全局最优

    不断调整最大子序和区间的起始位置,区间终止位置是不用调整的,因为区间的终止位置,在count取得最大值了,及时记录下来了。这相当于是用result记录最大子序和区间和(变相的算是调整了终止位置) 

    if (count > result) result = count;
    

    C++代码:

    1. class Solution {
    2. public:
    3. int maxSubArray(vector<int>& nums) {
    4. int result = INT32_MIN;
    5. int count = 0;
    6. for (int i = 0; i < nums.size(); i++) {
    7. count += nums[i];
    8. if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
    9. result = count;
    10. }
    11. if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
    12. }
    13. return result;
    14. }
    15. };
    • 时间复杂度:O(n)
    • 空间复杂度:O(1)

     二、动态规划 

    dp[i] : 表示包括 i 之前的最大连续子序列和

    i = 0,dp[0] = -2

    i = 1,count = (-2) + 1 = -1,在count 和 nums[1] = 1中选取最大值,即 dp[1] = max(dp[0] + nums[1],nums[1]); 所以dp[1] = 1

    i = 2,由于前面已经计算过包括i = 1之前的最大连续子序列和,并且将值保存在 dp[1] 里,所以count = dp[1] + (-3) = 1 + (-3) = -2,接着在count 和 nums[2] = -3中选取最大值,即 dp[2] = max(dp[1] + nums[2],nums[2]);所以dp[2] = -2,表示包括i = 2之前的最大连续子序列和。同理,如下推导

    i = 3,count = dp[2] + 4 = 2,dp[3] = max(2,4);所以dp[3] = 4。发现 count < nums[3],这时候取最大值就可以让dp[3] = nums[3],表示接下来,可以调整起点,让 i = 3 为起点

    i = 4,count = dp[3] + (-1) = 3,dp[4] = max(3,-1);所以dp[4] = 3.发现count > nums[4]的,表示可以保持让 i = 3为起点

    i = 5,count = dp[4] + 2 = 5,dp[5] = max(5,2);所以dp[5] = 5.发现count > nums[5]的,表示可以保持让 i = 3为起点

    i = 6,count = dp[5] + 1 = 6,dp[6] = max(6,1);所以dp[6] = 6.发现count > nums[6]的,表示可以保持让 i = 3为起点

    i = 7,count = dp[6] + (-5) = 1,dp[7] = max(1,-5);所以dp[7]=1.发现count > nums[7]的,表示可以保持让 i = 3为起点

    i = 8,count = dp[7] + 4 = 5,dp[8] = max(5,4);所以dp[8] = 5.发现count > nums[8]的,表示可以保持让 i = 3为起点 

    1. ① count = dp[i-1] + nums[i];
    2. ② dp[i] = max(count,nums[i]);
    3. ↓
    4. ↓
    5. ↓
    6. ↓
    7. ③ dp[i] = max(dp[i-1] + nums[i],dp[i]);

    >>动规五部曲

     1.确定dp数组(dp table)以及下标的含义

    • dp[i]:包括下标 i (以nums[i]为结尾)的最大连续子序列和为dp[i]

    2.确定递推公式

    • 第一种情况,在遍历nums[i]时,延续着前面连续子序列的和(dp[i-1]),
      • 即:dp[i-1] + nums[i];
    • 第二种情况,在遍历nums[i]时,不延续前面连续子序列的和(dp[i-1]),从头开始计算,
      • 即:nums[i];

    最后,我们取这两种情况的最大值,dp[i] = max(dp[i-1]+nums[i],nums[i]);

    3.数组初始化

    • 从递推公式可看出dp[i]依赖于dp[i-1]的状态,dp[0]就是递推公式的基础
    • dp[0] = nums[0]

    4.确定遍历顺序

    • 从递推公式可看出dp[i]依赖于dp[i-1]的状态,故需从前往后遍历

    5.举例推导dp数组

    1. class Solution {
    2. public:
    3. int maxSubArray(vector<int>& nums) {
    4. if (nums.size() == 0) return 0;
    5. vector<int> dp(nums.size(), 0); // dp[i]表示包括i之前的最大连续子序列和
    6. dp[0] = nums[0];
    7. int result = dp[0];
    8. for (int i = 1; i < nums.size(); i++) {
    9. dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移公式
    10. if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
    11. }
    12. return result;
    13. }
    14. };
    • 时间复杂度:O(n)
    • 空间复杂度:O(n)

    >>优化空间复杂度

    1. class Solution {
    2. public:
    3. // 动态规划 + 优化空间复杂度
    4. int maxSubArray(vector<int>& nums) {
    5. if(nums.size() == 0) return 0;
    6. int pre = nums[0];
    7. int result = nums[0];
    8. for(int i=1; i
    9. pre = max(pre + nums[i],nums[i]);
    10. if(pre > result) result = pre;
    11. }
    12. return result;
    13. }
    14. };
    • 时间复杂度:O(n)
    • 空间复杂度:O(1)

    参考和推荐文章、视频

    代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://programmercarl.com/0053.%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C.html#%E6%80%9D%E8%B7%AF

    代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://www.programmercarl.com/0053.%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C%EF%BC%88%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%EF%BC%89.html#%E6%80%9D%E8%B7%AF

    贪心算法的巧妙需要慢慢体会!LeetCode:53. 最大子序和_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1aY4y1Z7ya/?spm_id_from=333.788&vd_source=a934d7fc6f47698a29dac90a922ba5a3

    看起来复杂,其实是简单动态规划 | LeetCode:53.最大子序和_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV19V4y1F7b5/?spm_id_from=333.788&vd_source=a934d7fc6f47698a29dac90a922ba5a3

    来自代码随想录的课堂截图:

     

  • 相关阅读:
    ARM系统控制和管理接口System Control and Management Interface
    全国计算机技术与软件专业技术资格(水平)考试分数查询、2022软考分数线、系统分析师分数线、软考初级中级高级分数线
    CRMEB商城源码开源标准版v5.2.0+后端+前端uni-app开源包安装教程
    Spring框架原理 | IOC/DI | Bean
    532. 数组中的 k-diff 数对
    STM32F030在使用内部参考电压 (VREFINT)时与STM32G070的区别
    【Electron将HTML项目打包成桌面应用exe文件】
    八大排序汇总及其稳定性
    [ruby on rails]rails6.0升级6.1
    ElasticSearch从入门到精通:数据导入
  • 原文地址:https://blog.csdn.net/weixin_41987016/article/details/133497268
  • 最新文章
  • 【JVM】编译执行与解释执行的区别是什么?JVM 使用哪种方式?
    用 Hashids 优雅解决 C 端自增 ID 暴露问题
    V8引擎 精品漫游指南--Ignition篇(上) 指令 栈帧 槽位 调用约定 内存布局 基础内容
    LLVM Pass快速入门(四):代码插桩
    milkup:桌面端 markdown AI续写和即时渲染
    基于项目工程构建SBOM(软件物料清单)的研究
    鸿蒙应用开发UI基础第二节:鸿蒙应用程序框架核心解析与实操
    .NET 中如何快速实现 List 集合去重?
    扣子Coze实战:从0到1打造抖音+小红书热点监控智能体
    浅谈数据访问层
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
小工具 小游戏
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1

京公网安备 11010502049817号