【八】【C语言\动态规划】1567. 乘积为正数的最长子数组长度、413. 等差数列划分、978. 最长湍流子数组,三道题目深度解析

动态规划

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

动态规划与数学归纳法思想上十分相似。

数学归纳法:

  1. 基础步骤(base case):首先证明命题在最小的基础情况下成立。通常这是一个较简单的情况,可以直接验证命题是否成立。

  2. 归纳步骤(inductive step):假设命题在某个情况下成立,然后证明在下一个情况下也成立。这个证明可以通过推理推断出结论或使用一些已知的规律来得到。

通过反复迭代归纳步骤,我们可以推导出命题在所有情况下成立的结论。

动态规划:

  1. 状态表示:

  2. 状态转移方程:

  3. 初始化:

  4. 填表顺序:

  5. 返回值:

数学归纳法的基础步骤相当于动态规划中初始化步骤。

数学归纳法的归纳步骤相当于动态规划中推导状态转移方程。

动态规划的思想和数学归纳法思想类似。

在动态规划中,首先得到状态在最小的基础情况下的值,然后通过状态转移方程,得到下一个状态的值,反复迭代,最终得到我们期望的状态下的值。

接下来我们通过三道例题,深入理解动态规划思想,以及实现动态规划的具体步骤。

1567. 乘积为正数的最长子数组长度

题目解析

状态表示

状态表示一般通过经验+题目得到。

经验一般指以某个位置为结尾或者以某个位置为开始。

我们很容易可以定义这样一个dp状态表示,dp[i]表示以下标i为结尾的所有子数组中,乘积为正数的最长子数组长度。

我们可以尝试推导一下该状态表示的状态转移方程。

dp[i]表示以下标i为结尾的所有子数组中,乘积为正数的最长子数组长度。

我们关注所有以下标i为结尾的子数组,可以分为两种情况,第一种情况是子数组只有下标i一个元素,第二种情况是子数组不止下标i一个元素。

第一种情况,如果nums[i]>0,dp[i]=1,否则dp[i]=0。

第二种情况,我们可以将i下标元素单独分开,看成两部分。

如果nums[i]>0,dp[i]=dp[i-1]+1。

如果nums[i]=0,dp[i]=0。

如果nums[i]<0,dp[i]=(在第一部分所有子数组中,乘积为负数的最长子数组长度)+1。

我们发现还需要一个状态才能填写dp状态。所以我们应该添加一个状态,表示以下标i为结尾的所有子数组中,乘积为负数的最长子数组长度。

因此我们可以重新定义状态表示,

f[i]表示以下标i为结尾的所有子数组中,乘积为正数的最长子数组长度。

g[i]表示以下标i为结尾的所有子数组中,乘积为负数的最长子数组长度。

状态转移方程

根据上面的分析,可以分一下这些情况

  1. 如果只考虑i一个元素,

    • nums[i]>0时,f[i]=1,g[i]=0

    • nums[i]<0时,f[i]=0,g[i]=1

    • nums[i]=0时,f[i]=0,g[i]=0

  2. 如果不止考虑i一个元素,

    1. nums[i]>0时,考虑两种情况

      1. 第一种情况,f[i-1]=0,此时f[i]=0,因为我们考虑不止一个元素,所以最少是两个元素,子数组乘积一定是零,所以长度为0。 g[i-1]=0,此时g[i]=0,同理。

      2. 第二种情况,f[i-1]!=0,此时f[i]=f[i-1]+1,g[i]=g[i-1]+1。第一部分乘积为正数的最长子数组,后面添加nums[i]这个元素,乘积是正数乘以正数,是正数,所以f[i]=f[i-1]+1。 第一部分乘积为负数的最长子数组,后面添加nums[i]这个元素,乘积是负数乘以正数,是负数,所以g[i]=g[i-1]+1。

    2. nums[i]<0时,同样考虑两种情况

      1. 第一种情况,f[i-1]=0,此时f[i]=0,因为我们考虑不止一个元素,所以最少是两个元素,子数组乘积一定是零,所以长度为0。 g[i-1]=0,此时g[i]=0,同理。

      2. 第二种情况,f[i-1]!=0,此时f[i]=g[i-1]+1,g[i]=f[i-1]+1。第一部分乘积为负数的最长子数组,后面添加nums[i]这个元素,乘积是负数乘以负数,是正数,所以f[i]=g[i-1]+1。 第一部分乘积为正数的最长子数组,后面添加nums[i]这个元素,乘积是正数乘以负数,是负数,所以g[i]=f[i-1]+1。

    3. nums[i]=0时,f[i]=0,g[i]=0

我们可以整理一下上述情况,看看哪些情况是可以合并在一起的。

f[i]=max(如果只考虑i一个元素,如果不止考虑i一个元素)

g[i]=max(如果只考虑i一个元素,如果不止考虑i一个元素)

最终我们可以合并成这样,

 
        if(nums[i]==0) f[i]=g[i]=0;
        else if(nums[i]>0){
            f[i]=f[i-1]+1;
            g[i]=g[i-1]==0?0:g[i-1]+1;
        }else{
            f[i]=g[i-1]==0?0:g[i-1]+1;
            g[i]=f[i-1]+1;

        }

上述即状态转移方程。

初始化

根据状态转移方程我们知道,想要推导出i位置的状态,需要用到i-1位置的状态,所以我们需要初始化第一个状态,即f[0]、g[0]

 
    f[0]=nums[0]>0?1:0;
    g[0]=nums[0]<0?1:0;

填表顺序

根据状态转移方程我们知道,想要推导出i位置的状态,需要用到i-1位置的状态,所以我们应该从左往右填写,保证推导i位置状态时,i-1位置状态已经得到。

从左往右填写。

返回值

f[i]表示以下标i为结尾的所有子数组中,乘积为正数的最长子数组长度。

g[i]表示以下标i为结尾的所有子数组中,乘积为负数的最长子数组长度。

结合题目意思,我们应该遍历f数组中所有元素,记录最大的状态值,然后返回。

代码实现

 
int getMaxLen(int* nums, int numsSize){
    int n=numsSize;

    int f[n],g[n];
    f[0]=nums[0]>0?1:0;
    g[0]=nums[0]<0?1:0;

    int ret=nums[0]>0?1:0;
    for(int i=1;i<n;i++){
        if(nums[i]==0) f[i]=g[i]=0;
        else if(nums[i]>0){
            f[i]=f[i-1]+1;
            g[i]=g[i-1]==0?0:g[i-1]+1;
        }else{
            f[i]=g[i-1]==0?0:g[i-1]+1;
            g[i]=f[i-1]+1;

        }

        ret=fmax(ret,f[i]);

    }
    return ret;
}

413. 等差数列划分

题目解析

状态表示

状态表示一般由经验+题目得到

经验一般是,以某个位置为结尾,或者以某个位置为开始。

我们很容易可以定义这样一个dp状态表示,dp[i]表示以下标i为结尾的所有子数组中,是等差数组的子数组个数。

状态转移方程

dp[i]表示以下标i为结尾的所有子数组中,是等差数组的子数组个数。

对于 dp[i] 位置的元素 nums[i] ,会与前面的两个元素有下面两种情况:

  1. nums[i - 2], nums[i - 1], nums[i] 三个元素不能构成等差数列:那么以nums[i] 为结尾的等差数列就不存在,此时 dp[i] = 0 ;

  2. nums[i - 2], nums[i - 1], nums[i] 三个元素可以构成等差数列:那么以nums[i - 1] 为结尾的所有等差数列后面填上一个 nums[i] 也是一个等差数列,此时dp[i] = dp[i - 1] 。但是,因为nums[i - 2], nums[i - 1], nums[i] 三者又能构成一个新的等差数列,因此要在之前的基础上再添上一个等差数列,于是dp[i] = dp[i - 1] + 1 。

综上所述:状态转移方程为:

当: nums[i - 2] + nums[i] != 2 * nums[i - 1] 时, dp[i] = 0

当: nums[i - 2] + nums[i] == 2 * nums[i - 1] 时, dp[i] = 1 + dp[i - 1]

初始化

根据状态转移方程,我们知道,想要推导出i位置的状态,需要用到i-1,i-2位置的状态,所以我们需要初始化前两个位置的状态。

根据状态表示,dp[i]表示以下标i为结尾的所有子数组中,是等差数组的子数组个数。

很容易得到,

dp[0]=0

dp[1]=0

因为等差数组最少要有三个元素。

填表顺序

根据状态转移方程,我们知道,想要推导出i位置的状态,需要用到i-1,i-2位置的状态,所以我们推导i位置的状态时,i-1和i-2位置的状态应该已经得出。

所以填表顺序应该为,

从左往右。

返回值

根据状态表示,dp[i]表示以下标i为结尾的所有子数组中,是等差数组的子数组个数。

结合题目意思,我们应该遍历dp数组,统计以每个元素为结尾的等差子数组的个数。

代码实现

 

int numberOfArithmeticSlices(int* nums, int numsSize){
    int n=numsSize;
    if(n==1||n==2) return 0;
    int dp[n];
    dp[0]=0;
    dp[1]=0;
    int count=0;
    for(int i=2;i<n;i++){
        if(nums[i - 2] + nums[i] != 2 * nums[i - 1]) dp[i] = 0 ;
        else dp[i] = 1 + dp[i - 1];
        count+=dp[i];
    }

    return count;
}

978. 最长湍流子数组

题目解析

所谓湍流子数组,就是子数组中,每相邻的两个数呈现一上一下状态,交替出现。

状态表示

状态表示一般由经验+题目得到

经验一般是,以某个位置为结尾,或者以某个位置为开始。

子数组的求解,我们一般都可以以某个位置为结尾解决状态表示。

我们很容易可以定义这样一个dp状态表示,dp[i]表示以下标i为结尾的所有子数组中,最长湍流子数组的长度。

我们可以尝试推导一下该状态表示的状态转移方程。

dp[i]表示以下标i为结尾的所有子数组中,最长湍流子数组的长度。

我们想要通过dp[i-1]推导出dp[i],首先需要判断nums[i]和nums[i-1]的大小以及dp[i-1]表示的最长湍流子数组中,nums[i-1]处于“上升”状态还是“下降”状态。因此我们应该存储i元素处于“上升”还是“下降”状态。

我们可以重新定义状态表示,

f[i]表示以下标i为结尾的所有子数组中,i元素处于“上升”状态时,湍流子数组最长的长度。

g[i]表示以下标i为结尾的所有子数组中,i元素处于“下降”状态时,湍流子数组最长的长度。

状态转移方程

  1. 如果只考虑i唯一一个元素,f[i]=g[i]=1,最小的湍流子数组长度就是1。

  2. 如果不止考虑i一个元素,

    1. 如果nums[i]>nums[i-1],f[i]=g[i-1]+1,g[i]=1

    2. 如果nums[i]<nums[i-1],f[i]=1,g[i]=f[i-1]+1

    3. 如果nums[i]=nums[i-1],f[i]=g[i]=1

有很多情况,f[i],g[i]的值是1,我们可以初始化,把所有的数据初始化为1,把这些情况放到初始化里面解决。

所有我们只需要考虑剩下的情况,

得到状态转移方程为,

 
        if(arr[i]>arr[i-1]) f[i]=g[i-1]+1;
        else if(arr[i]<arr[i-1])g[i]=f[i-1]+1;

初始化

根据状态转移方程,我们知道,想要推导出i位置的状态,需要用到i-1位置的状态,

所以我们需要初始化第一个位置的状态。

同时在状态转移方程推导时,我们将许多情况放到初始化阶段来解决,所以我们同时应该初始化所有数据为1。

如果只考虑第一个位置,我发现f[0]=g[0]=1。

所以我们只需要把所有数据初始化为1即可。

填表顺序

根据状态转移方程,我们知道,想要推导出i位置的状态,需要用到i-1位置的状态,

所以我们在推导i位置的状态时,应该已经得到i-1位置的状态。

所以我们应该从左往右填写,一次填写两个表。

返回值

f[i]表示以下标i为结尾的所有子数组中,i元素处于“上升”状态时,湍流子数组最长的长度。

g[i]表示以下标i为结尾的所有子数组中,i元素处于“下降”状态时,湍流子数组最长的长度。

结合题意,我们应该遍历f数组,记录最大的湍流子数组长度。

代码实现

 
int maxTurbulenceSize(int* arr, int arrSize) {
    int n=arrSize;

    int f[n],g[n];
    for(int i=0;i<n;i++){
        f[i]=1;
        g[i]=1;
    }

    int ret=1;
    for(int i=1;i<n;i++){
        if(arr[i]>arr[i-1]) f[i]=g[i-1]+1;
        else if(arr[i]<arr[i-1])g[i]=f[i-1]+1;

        ret=fmax(ret,fmax(f[i],g[i]));
    }
    return ret;
}

结尾

今天我们学习了动态规划的思想,动态规划思想和数学归纳法思想有一些类似,动态规划在模拟数学归纳法的过程,已知一个最简单的基础解,通过得到前项与后项的推导关系,由这个最简单的基础解,我们可以一步一步推导出我们希望得到的那个解,把我们得到的解依次存放在dp数组中,dp数组中对应的状态,就像是数列里面的每一项。最后感谢您阅读我的文章,对于动态规划系列,我会一直更新,如果您觉得内容有帮助,可以点赞加关注,以快速阅读最新文章。

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/276139.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git 学习 之一个规范的 commit 如何写

最好的话做一件完整的事情就提交一次

状态管理概述

ArkTS UI的状态管理到这里就叙述完了&#xff0c;现在做一个概述&#xff0c;也可以认为是一个总结。 在声明式UI编程框架中&#xff0c;UI是程序状态的运行结果&#xff0c;用户构建了一个UI模型&#xff0c;其中应用的运行时的状态是参数。当参数改变时&#xff0c;UI作为返回…

Vue(一):Vue 入门与 Vue 指令

Vue 01. Vue 快速上手 1.1 Vue 的基本概念 用于 构建用户界面 的 渐进性 框架 构建用户界面&#xff1a;基于数据去渲染用户看到的界面渐进式&#xff1a;不需要学习全部的语法就能完成一些功能&#xff0c;学习是循序渐进的框架&#xff1a;一套完整的项目解决方案&#x…

探索Apache Commons Imaging处理图像

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;咱们今天来聊聊图像处理。在这个数字化日益增长的时代&#xff0c;图像处理已经成为了一个不可或缺的技能。不论是社交媒体上的照片编辑&#xff0c;还是专业领域的图像分析&#xff0c;图像处理无处不在。而作为…

JavaSE50题:26. (数组练习题)使奇数位于偶数之前

概述 调整数组顺序使得奇数位于偶数之前&#xff0c;调整之后&#xff0c;不关心大小顺序。 如数组&#xff1a;{1,2,3,4,5,6} 调整后可能是&#xff1a;{1&#xff0c;5&#xff0c;3&#xff0c;4&#xff0c;2&#xff0c;6} 方法 定义 left 和 right&#xff0c;二者分别…

位运算|比特位计数、汉明距离

位运算|比特位计数、汉明距离 338 比特位计数 /** 比特位计数法一&#xff1a;Brian Kernighan 算法的原理是&#xff1a;对于任意整数 x&#xff0c;令 xx & (x−1)&#xff0c;该运算将 x 的二进制表示的最后一个 1 变成 0。因此&#xff0c;对 x 重复该操作&#xff0…

中职网络安全Web2003-2——Web渗透测试

需要环境或换&#xff0c;有问题可以私信我或加Q 1.通过URL访问http://靶机IP/1&#xff0c;对该页面进行渗透测试&#xff0c;将完成后返回的结果内容作为Flag值提交&#xff1b; FLAGflag{htmlcode} 2.通过URL访问http://靶机IP/2&#xff0c;对该页面进行渗透测试&#xff…

数字钥匙进入3.0时代,他们要做智能汽车时代的「微信」

“假设用我们的即时通讯的工具来说&#xff0c;我们想造一个微信&#xff0c;我们希望跟更多的主机厂拥抱融合&#xff0c;而不是造一个飞信。” 11月24日&#xff0c;在银基科技承办的第三届数字钥匙及生态大会期间&#xff0c;银基科技CEO单宏寅尝试向到场的媒体&#xff0c…

Flink Kafka[输入/输出] Connector

本章重点介绍生产环境中最常用到的Flink kafka connector。使用Flink的同学&#xff0c;一定会很熟悉kafka&#xff0c;它是一个分布式的、分区的、多副本的、 支持高吞吐的、发布订阅消息系统。生产环境环境中也经常会跟kafka进行一些数据的交换&#xff0c;比如利用kafka con…

代码随想录刷题题Day24

刷题的第二十四天&#xff0c;希望自己能够不断坚持下去&#xff0c;迎来蜕变。&#x1f600;&#x1f600;&#x1f600; 刷题语言&#xff1a;C Day24 任务 ● 491.递增子序列 ● 46.全排列 ● 47.全排列 II 1 递增子序列 491.递增子序列 思路&#xff1a; 本题求自增子序…

Translation翻译插件

Translation插件是为IntelliJ IDEA开发的&#xff0c;因此只能在IntelliJ IDEA中使用。但是&#xff0c;如果你需要在其他软件中进行翻译&#xff0c;可以考虑使用其他的翻译工具或服务。例如&#xff0c;一些在线翻译网站&#xff08;如Google翻译、百度翻译等&#xff09;提供…

由浅入深走进Python异步编程【协程与yield】(含代码实例讲解 || 迭代器、生成器、协程、yield from)

写在前面 从底层到第三方库&#xff0c;全面讲解python的异步编程。这节讲述的是python异步编程的底层原理第一节&#xff0c;详细了解需要配合下一节观看哦。纯干货&#xff0c;无概念&#xff0c;代码实例讲解。 本系列有6章左右&#xff0c;点击头像或者专栏查看更多内容&…

C++学习实践(一)高频面试问题总结(附详细答案)

文章目录 一、基础常见面试题1、数组和链表区别2、深拷贝和浅拷贝相关问题的区别3、a和a区别4、c内存模型5、四种强制转换和应用场景 二、指针相关1、指针和引用的区别2、函数指针和指针函数3、传指针、引用和值4、常量指针和指针常量5、野指针6、智能指针的用法 三、关键字作用…

YOLOv8可视化:引入多种可视化CAM方法,为科研保驾护航

💡💡💡本文内容:调用pytorch下的CAM可视化库,支持十多种可视化方法,打开“黑盒”,让YOLOv8变得相对可解释性 收录 YOLOv8原创自研 https://blog.csdn.net/m0_63774211/category_12511737.html?spm=1001.2014.3001.5482 💡💡💡全网独家首发创新(原创),适…

桥接模式-举例

概叙&#xff1a;桥接模式用一种巧妙的方式处理多层继承存在的问题&#xff0c; 用抽象关联取代了传统的多层继承&#xff0c; 将类之间的静态继承关系转换为动态的对象组合关系&#xff0c; 使得系统更加灵活&#xff0c;并易于扩展&#xff0c; 同时有效控制了系统中类的个数…

企业如何购买腾讯云服务器?(详细指南)

腾讯云服务器购买流程直接在官方秒杀活动上购买比较划算&#xff0c;在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵&#xff0c;但是自定义购买云服务器CPU内存带宽配置选择范围广&#xff0c;活动上购买只能选择固定的活动机&#xff0c;选择范围窄&#xff0c;但是…

专题四:前缀和

前缀和 一.一维前缀和(模板)&#xff1a;1.思路一&#xff1a;暴力解法2.思路二&#xff1a;前缀和思路 二. 二维前缀和(模板)&#xff1a;1.思路一&#xff1a;构造前缀和数组 三.寻找数组的中心下标&#xff1a;1.思路一&#xff1a;前缀和 四.除自身以外数组的乘积&#xff…

php最常出现的错误

目录 1. E_WARNING&#xff1a;为 foreach &#xff08;&#xff09;提供的参数无效 2. PDOException&#xff1a;拒绝SQLSTATEHY000连接 3.错误使用empty函数 1. E_WARNING&#xff1a;为 foreach &#xff08;&#xff09;提供的参数无效 PHP foreach构造在PHP 4中引入&am…

web3方向产品调研

每次互联网形态的改变&#xff0c;都会对世界产生很大的影响&#xff0c;上一次对社会产生重大影响的互联网形态&#xff08;Web2.0&#xff09;催生了一批改变人类生活和信息交互方式的企业。 目录 概述DAO是什么&#xff1f;为什么我们需要DAO? 金融服务金融桥接及周边服务D…

Unity中Shader 齐次坐标

文章目录 前言一、什么是齐次坐标二、齐次坐标增加分量 w 的意义1、当 w ≠ \neq  0时&#xff1a;2、当 w 0时&#xff1a;3、用方程组&#xff0c;直观的看一下w的意义 前言 在之前的文章中&#xff0c;我们进行了正交相机视图空间转化到裁剪空间的推导。 Unity中Shade…