【动态规划】NK刷题记之DP6 连续子数组最大和(C语言实现)

这篇具有很好参考价值的文章主要介绍了【动态规划】NK刷题记之DP6 连续子数组最大和(C语言实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

❤️博客主页:小镇敲码人
🍏 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌞勤奋努力是一个长期的过程,如果追求速成,就是异想天开。你越努力、越认真生活,生活就会变得越美丽。如果一直保持奋斗,你的人生将会发生翻天覆地的变化。
🍉 如果你也迷失在了路上,对人生充满了迷惘,不要害怕,冷静下来,慢慢的自救,不断求知,让自己变得更加优秀吧!!!😃😃😃

一、题目

老规矩,给出牛客网原题链接,感兴趣的小伙伴可以去做一下,虽然是简单题,但事情总是从难到易的嘛点击此处跳转

给定一个长度为 n 的数组,数组中的数为整数。 请你选择一个非空连续子数组
, 使该子数组所有数之和尽可能大,子数组最小长度为1。求这个子数组最大值。

二、题解

2.1动态规划

1.定义dp[i]为尾项为a[i]的连续子数组的最大和

2.那么可以得到递推式:dp[i] = Max(dp[i-1]+a[i],a[i])

注意:dp[i]在(dp[i-1]+a[i])和a[i]两者中取一个较大值,恰好完美的保证了dp[i]是尾项为a[i]的连续子数组的最大和
那为什么说它保证了连续呢,因为按照定义dp[i-1]也是连续的。

3.得到了递推式就很简单了,利用递归和递推都可以实现
   这里对于首项和尾项给出一个定义,如图所示
【动态规划】NK刷题记之DP6 连续子数组最大和(C语言实现)

2.2贪心算法

2.1.1 贪心算法的定义

首先我们得先了解一下什么叫做贪心,我的理解是这样的:选择对当前而言最优的一个解,从而能达到整体最优,什么意思呢?
举几个例子来说明一下吧:
1.动物世界中,蟒蛇吃鳄鱼时,蟒蛇不会一口吃下去,而是一点一点的吃,这样一次看起来只吃了一点点,但整体看上去蟒蛇最终还是把鳄鱼吃完了。

【动态规划】NK刷题记之DP6 连续子数组最大和(C语言实现)
但要注意贪心思维并不适用所有的场景,比如下面生活中的实例
 2.有些人常说,人生苦短,要及时行乐所以每一次面临抉择时都会选择让自己更舒服的一个方案,这虽然看起来局部上面好像是开心了,但以大局观去看,整体上并不是最优的,例如小帅是一个喜欢快乐的男生,他总是喜欢打游戏到深更半夜,父母非常苦恼,但当父母说他时,他总回答:“哎呀,人生苦短,要及时行乐嘛”,对此父母也不好说什么,其实这就是小帅陷入贪心思维,只顾眼前,但长期之后身体反而会出现问题,这便是局部最优没有造成整体最优😁😁

 理解了什么是贪心算法,我们还要知道它的两个性质

2.2.2贪心算法的性质

贪心算法求解具有两个重要的性质:贪心选择性质和最优子结构性质。
(1)贪心选择性质
所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心法与动态规划法的主要区别。
(2)最优子结构性质
该问题解的整体最优性依赖于其局部子问题解的最优性这种性质是可以采用贪心算法解决问题的关键特征。
举例:如最小生成树求解。

2.2.3本题的贪心算法解决思路

1.定义一个sum,它表示遍历到以ai为尾项时的一个连续子数组的局部最优解,且sum必须要大于0,因为只有这样对于a[i]来说,当以它为尾项时那个连续子数组的值才是最优的。
2.sum = Max(0,sum+a[i]),可以看到如果sum的初始值设为sum = Max(0,a[0]),那么sum >= 0,对于a[i]来说,当它为尾项时,就可以一直保证局部最优了。
3.创建一个变量ret,因为在遍历时,sum+a[i]可能是小于0的,那我们怎样确定这些局部最优里面,谁才是最优的呢,这个时候我们的ret变量就可以起到一个维护最大值的作用。

2.2.4贪心与动态规划的区别

我的理解是它们有一下几点区别:
1️⃣解决的问题不同:
贪心算法重点的问题是否做到了局部最优,它通常对后面的问题的影响是可估计的,而动态规划则是把一个很大的问题分成很多子问题去求
2️⃣ 能否获得最优解:

贪心算法可能会得不到最优解,但动态规划一定可以得到最优解
3️⃣算法复杂度不同:
通常动态规划的时间复杂度和空间复杂度都会比贪心算法的要大。具体大家可以看看这篇文章点击此处直接跳转—>动态规划与贪心的区别

三、代码实现

3.1法一:动态规划(递归实现)法

3.1.1创建变量n,并读入数据

  int n = 0;
  scanf("%d",&n);

3.1.2创建动态数组

  int* a = (int*)malloc(n * sizeof(int));//创建动态数组a存储数据
  int*dp = (int*)malloc((n+1) * sizeof(int));//创建动态数组dp用来存储是以a[i]为尾项的连续数组最大值

3.1.3对动态数组进行断言,并赋初值

  assert(dp && a);//断言a和dp,防止空间申请不成功出现空指针的情况
  memset(a, 0, (n * sizeof(int));
  dp[0] = -1e9;//特殊情况i == 0将dp[0]设置的很小,便于我们得
              //出dp[i]的首项,这一点后面也会有说明,看
              //不懂先不要着急,且听我慢慢道来

3.1.4读入数据

   /*!!!注意i从1开始,因为递推式
         中存在dp[i-1],防止数组越界*/
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }

3.1.5创建递归函数

  summax(n, n, a, dp);
3.1.5.1实现递归函数
int summax(int n, int i, int* a, int* dp) {
    if (i == 1)
        return dp[i] = max(dp[i - 1] + a[i], a[i]);
    return dp[i] = max(summax(n, i - 1, a, dp) + a[i], a[i]);
}

注意这里的i == 1的情况也正照应了我们为什么要将dp[0]设置的很小,是因为我们的数组下标是从1开始存入数据的,防止数组越界,而dp[1] = a[1],所以将dp[0]设置的很小,其实这里像下面那样改就没有这么多事了,但没办法我这该死的仪式感😆😆

int summax(int n, int i, int* a, int* dp) {
    if (i == 1)
        return dp[i] = a[i];    
    return dp[i] = max(summax(n, i - 1, a, dp) + a[i], a[i]);
}

3.1.6将动态数组排序

这里我们调用了一下快排函数需要引用头文件<stdlib.h>,并且需要自己实现一下比较器,这里我们一起给出,那什么是比较器呢?后续我们会出一期来专门介绍,大家可以先自行查阅资料

/* 下面是一个比较器
  被快排函数用作比较,(a-b)> 0交换顺序
  所以是一个升序的排序 */
int cmp(const void* a, const void* b) {
    return *(int*)a - *(int*)b;
}
 qsort(dp, n + 1, sizeof(int), cmp);//升序排序最后一项为最大值

3.1.7打印结果

 printf("%d", dp[n]);
 return 0;

3.1.8完整C语言代码

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
/*
  下面是一个比较器
  被快排函数用作比较,(a-b)> 0交换顺序
  所以是一个升序的排序
  */
int cmp(const void* a, const void* b) {
    return *(int*)a - *(int*)b;
}
/*
    判断a,b的大小并
返回较大的值
*/
int max(int a, int b) {
    if (a > b)
        return a;
    return b;
}
int summax(int n, int i, int* a, int* dp) {
    if (i == 1)
        return dp[i] = max(dp[i - 1] + a[i], a[i]);
    return dp[i] = max(summax(n, i - 1, a, dp) + a[i], a[i]);
}
int main() {
    int n = 0;
    scanf("%d", &n);
    int* a = (int*)malloc(n * sizeof(int));
    int* dp = (int*)malloc((n + 1) * sizeof(int));
    assert(a && dp);//断言防止动态数组空间申请不成功
    memset(a, 0, (n + 1)* sizeof(int));
    dp[0] = -1e9;//特殊情况i == 0将dp[0]设置的很小,便于我们得出dp[i]的首项
    /*将数据存入
         动态数组a中*/
    /*!!!注意i从1开始,因为递推式
         中存在dp[i-1],防止数组越界*/
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    summax(n, n, a, dp);//注意i传的值,这是与思路二递归的区别之一
    qsort(dp, n + 1, sizeof(int), cmp);//升序排序最后一项为最大值
    printf("%d", dp[n]);
    return 0;
}

3.2法二:贪心

前几步创建变量的步骤基本与法一相同,这里我们不再做过多的叙述

注意由于我们的sum只接受大于0的数,当给的数据全为负数时,我们的贪心就失效了,所以要特判一下全为负数的情况

3.2.1创建标志变量并录入数据

注意标志变量flag用来判断数据是否全为负数

int flag = 0;//标记全是负数的情况
 for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
        if (a[i] >= 0)
         flag = 1;
    }

3.2.2考虑全部为负数的情况

 if (!flag) 
 {
      int ret = a[0];
        /*求出负数里面的较大值*/
       for (int i = 1; i < n; i++) 
       {
            ret = Max(ret, a[i]);
        }
        printf("%d", ret);
        return 0;//提前结束main函数防止其继续进行下去
    }

3.2.3考虑不全为负数的情况

  int ret = 0;//初始化ret和sum的值
  int sum = 0;
  for(int i = 0;i < n;i++)
  {
    sum = Max(0,sum+a[i]);//只要sum的值大于0,对于当前的a[i],就是最优的,即以a[i]为尾项的连续子数组之和的最大值,
                               // 反之,sum<0,我们需将sum重置为0,因为这会对后面的值产生影响,从而得不到局部最优
    ret = Max(ret,sum);//从局部最优解里面选出一个整体最优解,这就是我们需要的答案
  }

3.2.4打印最优解

printf("%d",ret);

3.2.5实现求最大值函数Max

/*
  返回较大值
*/
int Max(int a, int b) {
    if (a > b)
        return a;
    return b;
}

3.2.6C语言完整代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
/*
  返回较大值
*/
int Max(int a, int b) {
    if (a > b)
        return a;
    return b;
}
int main() {
    int n = 0;
    scanf("%d", &n);
    int* a = (int*)malloc(n * sizeof(int));//创建一个动态数组来存储数据
    assert(a);//对指针a进行断言,防止出现空指针的情况
    int flag = 0;//标记全是负数的情况
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
        if (a[i] >= 0) flag = 1;
    }
    /*
     全为负数的情况
    */
    if (!flag) {
        int ret = a[0];
        /*求出负数
              里面的较大值*/
        for (int i = 1; i < n; i++) {
            ret = Max(ret, a[i]);
        }
        printf("%d", ret);
        return 0;//提前结束main函数防止其继续进行下去
    }
    int ret = 0; //将ret和sum的值初始化
    int sum = 0;
    /*
       不全为负数的情况
    */
    for (int i = 1; i < n; i++) {
        sum = Max(0, sum +a[i]);//只要sum的值大于0,对于当前的a[i],就是最优的,即以a[i]为尾项的连续子数组之和的最大值,
                               // 反之,sum<0,我们需将sum重置为0,因为这会对后面的值产生影响,从而得不到局部最优
        ret = Max(ret,sum);//从局部最优解里面选出一个整体最优解,这就是我们需要的答案
    }
    printf("%d", ret);//打印这个整体最优解
    return 0;
}

四、总结与思考

这上面的两种方法还有不同的实现方式,比如法一的动态规划,就可以用递推的方式,这样可以节省o(n)的额外空间,法二用贪心的思维去解,也节省了空间,这便是贪心的优势所在,另外提一嘴,这两种方法都可以反过来求,例如第一种动态规划的解法dp[i]如果定义为首项为a[i]的连续子数组的最大和一样是可以解决这个题目的,这里由于篇幅不再展开,最后本人才疏学浅,若对以上文章有任何疑问,欢迎大家在评论区与我讨论,或者私信交流都是可以的哦😘😘文章来源地址https://www.toymoban.com/news/detail-473530.html

到了这里,关于【动态规划】NK刷题记之DP6 连续子数组最大和(C语言实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 题解53 | #动态规划#连续子数组的最大和(一)(二)#

    题解 | #链表中倒数第k个结点# /** * struct ListNode { * int val; * struct ListNode *next; * }; *//** * * @par   第一次面试 c++后端开发 会问什么呀,第一次面试没一点经验   题解 | #求二叉树的层序遍历# # class TreeNode:# def __init__(self, x):# self.val = x# sel   题解 | #牛客网连续练习题目3天及以上的

    2024年02月04日
    浏览(70)
  • (动态规划) 剑指 Offer 42. 连续子数组的最大和 ——【Leetcode每日一题】

    难度:简单 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 要求时间复杂度为 O(n) 。 示例1: 输入: nums = [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 提示 : 1 = a r r . l e n g t h = 1 0 5 1 = arr.length = 10^

    2024年02月11日
    浏览(53)
  • 动态规划(一) 变态青蛙跳台阶、最大连续子数组和、字符串分割 附源码讲解

    实现 import java.util.*; public class Main { public static void main(String[] args) { Scanner scan = new Scanner(System.in); while (scan.hasNext()){ int length = scan.nextInt(); int[] array = new int[length]; for (int i = 0; i length; i++) { array[i] = scan.nextInt(); } System.out.println(findResult(array)); } } private static int findResult(int[] array) {

    2024年04月17日
    浏览(55)
  • 力扣 53. 最大子数组和(C语言+分治递归、动态规划)

            给你一个整数数组  nums  ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组  是数组中的一个连续部分。         示例 1:         示例 2:         示例 3: (1)分治递归         分治法的核心部分。

    2024年02月07日
    浏览(37)
  • 【七】【C语言\动态规划】最大子数组和、环形子数组的最大和、乘积最大子数组,三道题目深度解析

    动态规划就像是解决问题的一种策略,它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题,并将每个小问题的解保存起来。这样,当我们需要解决原始问题的时候,我们就可以直接利用已经计算好的小问题的解,而不需要重

    2024年02月03日
    浏览(46)
  • 【动态规划基础】求最大连续子序列和——最大子段和

    给出一个长度为 n n n 的序列 a a a ,选出其中连续且非空的一段使得这段和最大。 第一行是一个整数,表示序列的长度 n n n 。 第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i a i ​ 。 输出一行一个整数表示答案。 样例输入 样例输出 样例 1 解释 选取

    2024年02月03日
    浏览(50)
  • 两个数组的dp问题(2)--动态规划

      一)交错字符串: 97. 交错字符串 - 力扣(LeetCode) 一)确定一个状态标识: 如果我选择s1的一段区间,再进行选择s2得一段区间,那么s3这个字符串的长度就已经固定 预处理:在s1字符串s2字符串和s3字符串前面加上一个虚拟字符,让下标从1开始进行计数 如果要是从1号位置开始进

    2024年02月15日
    浏览(41)
  • 动态规划:两个数组的dp问题(C++)

    动态规划往期文章: 动态规划入门:斐波那契数列模型以及多状态 动态规划:路径和子数组问题 动态规划:子序列问题 动态规划:回文串问题 1.最长公共子序列(中等) 链接 :最长公共子序列 题目描述 做题步骤 状态表示 对于两个数组的dp,采用一维dp是没有办法清晰的表

    2024年02月08日
    浏览(50)
  • C++--动态规划两个数组的dp问题

    1.最长公共子序列  力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 给定两个字符串  text1 和  text2 ,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的  子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的

    2024年02月10日
    浏览(40)
  • 从01背包开始动态规划:暴力解法 + dp + 滚动数组 + dp优化

        01背包问题是动态规划中最经典的问题之一,本篇将通过给出其四种解法,使读者更深刻理解动态规划。   有N件物品和一个容量是 V 的背包,每个物品有各自的体积和价值,且每个物品只能放一次(这也是01背包名字的由来),如何让背包里装入的物品具有最大的价值总

    2024年04月17日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包