【五】【C语言\动态规划】删除并获得点数、粉刷房子、买卖股票的最佳时机含冷冻期,三道题目深度解析

动态规划

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

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

数学归纳法:

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

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

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

动态规划:

  1. 状态表示:

  2. 状态转移方程:

  3. 初始化:

  4. 填表顺序:

  5. 返回值:

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

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

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

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

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

740. 删除并获得点数

题目解析

我们发现选择了一个数字,假设是x,那么x+1和x-1我们都不能再选择,这种模式非常像打家劫舍题目的模式。由此我们可以把此问题转化为打家劫舍问题。

由于1<=nums[i]<=1e4,所以我们可以创建一个1e4+1大小的数组count,count[i]表示i数字拥有的点数,也就是i*(i出现的次数),这样我们就可以把原问题转化为打家劫舍问题。

状态表示

我们将nums数组转化为count数组,接着对count数组分析即可。

我们可以定义dp[i]表示,1到i 这些数中,我们选择其中不相邻的部分项,所能得到的最大点数。

状态转移方程

我们想一想dp[i]怎么由其他的状态推导出来。

dp[i]表示,1到i 这些数中,我们选择其中不相邻的部分项,所能得到的最大点数。

dp[i-1]表示,1到i-1 这些数中,我们选择其中不相邻的部分项,所能得到的最大点数。

dp[i-2]表示,1到i-2 这些数中,我们选择其中不相邻的部分项,所能得到的最大点数。

我们对i的状态进行研究,一共可以分为两种情况,要么1-i这些数中,我们选择其中不相邻的部分项,其中选择了i,要么我们不选择i。

当我们选择i,那我们就不可能选择i-1,所以此时最大的点数应该是dp[i-2]+count[i]

如果我们不选择i,那我们就可以选择i-1,此时最大的点数应该是dp[i-1],也就是从1到i-1这些数选择不相邻的部分项所能获得的最大点数。

故状态转移方程为,dp[i]=max(dp[i-2]+count[i],dp[i-1])

初始化

由状态转移方程我们知道,想要推导出i位置的状态,需要用到i-1和i-2位置的状态,所以我们需要初始化前两个位置的状态,也就是下标1和2。因为count[0]是不考虑的,nums>=1。

很容易可以得出。

dp[0]=count[0]

dp[1]=max(count[0],count[1])

填表顺序

从左到右

返回值

返回最后一个元素即dp[1e4]

代码实现

 
int deleteAndEarn(int* nums, int numsSize) {
    int n=numsSize;
    const int m=1e4+1;
    int count[m];
    memset(count,0,sizeof(count));
    for(int i=0;i<n;i++){
        count[nums[i]]+=nums[i];
    }

    int dp[m];
    dp[1]=count[1];
    dp[2]=fmax(count[1],count[2]);

    for(int i=3;i<=1e4;i++){
        dp[i]=fmax(dp[i-2]+count[i],dp[i-1]);
    }
    return dp[m-1];
}

我们知道nums中存储的数字的范围是1到1e4,所以我们可以利用count数组, 定义count[i]表示i数字拥有的点数,我们只需要对count数组做一次打家劫舍就可以了。

LCR 091. 粉刷房子

题目解析

状态表示

如果我们定义dp[i]表示从0号房子到i号房子,我们选择不同的粉刷方案,所花费的最小金额数,我们想一想此状态表示的状态转移方程怎么推导?我们具体分析i号房子的状态,一共有三种情况,要么i号房子粉刷成红色,要么i号房子粉刷成绿色,要么i号房子粉刷成蓝色。对于这三中国情况,我们没办法只通过前面几个房子的最小花费数而得到i号房子的最小花费数,因为当我确定i号房子的粉刷颜色,前面的房子的粉刷颜色我没办法确定。所以我们的状态表示应该还需要包括粉刷的颜色。

我可以对其改进,定义dp[i][j]表示从0号房子开始到i号房子,选择不同的粉刷方案,且i号房子粉刷j颜色时的最小花费。其中j的取值是0、1或者2,分别表示红色绿色蓝色。

这样我们就是推导出状态转移方程,一步一步推导状态。

状态转移方程

我们想一想dp[i][j]的状态值如何通过其他的状态推导出来。

dp[i][j]表示从0号房子开始到i号房子,选择不同的粉刷方案,且i号房子粉刷j颜色时的最小花费。

dp[i-1][j]表示从0号房子开始到i-1号房子,选择不同的粉刷方案,且i号房子粉刷j颜色时的最小花费。

对于i号房子,我们依旧分为三种情况,要么粉刷红色要么粉刷绿色要么粉刷蓝色。

当我们对i号房子粉刷红色时,很明显,dp[i][0]=min(dp[i-1][1],dp[i-1][2])+costs[i][0]

i-1号房子粉刷的颜色不能是红色,要么是绿色要么是蓝色,从0号房子到i-1号房子,且i-1号房子粉刷成绿色蓝色时最小的花费分别是dp[i-1][1],dp[i-1][2]

选择较小的一个然后加上i号房子粉刷红色的花费就是dp[i][0]

同理dp[i][1]=min(dp[i-1][0],dp[i-1][2])+costs[i][1]

dp[i][2]=min(dp[i-1][0],dp[i-1][1])+costs[i][2]

故,状态转移方程为

dp[i][0]=min(dp[i-1][1],dp[i-1][2])+costs[i][0]

dp[i][1]=min(dp[i-1][0],dp[i-1][2])+costs[i][1]

dp[i][2]=min(dp[i-1][0],dp[i-1][1])+costs[i][2]

初始化

根据状态转移方程,我们知道推导出i房子粉刷红绿蓝三个状态需要i-1号房子的三个状态,所以我们需要初始化第一个房子的三个状态值。

即,

dp[0][0]=costs[0][0]; dp[0][1]=costs[0][1]; dp[0][2]=costs[0][2];

填表顺序

从左往右填写,一次填写三个状态

返回值

返回最后一个房子三个状态中最小的那个花费。

即,return fmin(dp[n-1][0],fmin(dp[n-1][1],dp[n-1][2]));

代码实现

 

int minCost(int** costs, int costsSize, int* costsColSize){
    int n=costsSize;
    int dp[n][3];

    dp[0][0]=costs[0][0];
    dp[0][1]=costs[0][1];
    dp[0][2]=costs[0][2];

    for(int i=1;i<n;i++){
        dp[i][0]=fmin(dp[i-1][1],dp[i-1][2])+costs[i][0];
        dp[i][1]=fmin(dp[i-1][0],dp[i-1][2])+costs[i][1];
        dp[i][2]=fmin(dp[i-1][0],dp[i-1][1])+costs[i][2];
    }

    return fmin(dp[n-1][0],fmin(dp[n-1][1],dp[n-1][2]));
}

309. 买卖股票的最佳时机含冷冻期

题目解析

状态表示

如果我们定义dp[i]表示从第0天开始到第i天,我们所得到利润的最大值,我们没办法通过其他的状态值推导出i位置的状态值。仔细想想,其实第i天的状态还可以细分。

可以分为第i天结束时,手上有股票,或者手上没股票且在冷冻期,或者手上没股票且不在冷冻期。

所以我们可以定义 dp[i][j]表示从第0天到第i天,所获得的最大利润值,且第i天结束时拥有状态j(j=0表示手上有股票,j=1表示手上没股票,且在冷冻期,j=2表示手上没股票,且不在冷冻期)。

状态转移方程

我们想一想第i天的三个状态能不能由其他的状态推导得出。

dp[i][j]表示从第0天到第i天,所获得的最大利润值,且第i天结束时拥有状态j

dp[i-1][j]表示从第0天到第i-1天,所获得的最大利润值,且第i天结束时拥有状态j

(j=0表示手上有股票,j=1表示手上没股票,且在冷冻期,j=2表示手上没股票,且不在冷冻期)。

当第i天结束的时候,我们手上有股票,对应dp[i][0],说明第i天白天我们买了股票,或者第i-1天我们手上有股票,且第i天白天我们没有卖掉。

也就是第i-1天结束时,要么是手上有股票,要么是手上没股票且不在冷冻期。

所以dp[i][0]=max(dp[i-1][0],dp[i-1][2]-prices[i])

第i天买了股票,所以利润需要减去股票价值。

当第i天结束的时候,我们手上没有股票,且在冷冻期,对应dp[i][1],说明第i天白天我们卖了股票,所以在第i天结束的时候处于冷冻期。所以要么第i-1天手上有股票,然后第i天白天我们卖掉了,要么第i-1天我们手上没有股票且不在冷冻期,第i天白天我们买了股票又卖了股票。

所以dp[i][1]=max(dp[i-1][0]-prices[i],dp[i-1][2])

当第i天结束的时候,我们手上没有股票,且不在冷冻期,对应 dp[i][2],说明第i天白天我们没有卖股票,要么i-1天白天我们卖了股票,要么i-1天白天也没卖股票,然后第i天白天什么都没做。也就是i-1天结束时的状态要么是手上没股票,且在冷冻期,要么是手上没股票,且不在冷冻期。(i-1天结束时的状态要么是手上没股票,且在冷冻期,然后冷冻期一直持续到第i天结束前,结束时冷冻期结束。)

所以dp[i][2]=max(dp[i-1][1],dp[i-1][2])

故状态转移方程为,

dp[i][0]=fmax(dp[i-1][0],dp[i-1][2]-prices[i]); dp[i][1]=fmax(dp[i-1][0]+prices[i],dp[i-1][2]); dp[i][2]=fmax(dp[i-1][1],dp[i-1][2]);

初始化

通过状态转移方程,我们知道推导出i天的状态需要i-1天的状态,所以我们需要初始化第0天的三个状态。

dp[0][0]表示第0天结束时手上有股票,说明白天买了股票,利润为负的prices[0]。

dp[0][1]表示第0天结束时手上没有股票,且在冷冻期,不可能存在的状态,所以该状态的值不能影响后面的状态,利润置0就可以了。因为会取到这个值的状态转移方程是dp[i][2]=fmax(dp[i-1][1],dp[i-1][2]);利润置0,dp[i-1][2]不为零,就不会取到dp[i-1][1],dp[i-1][2]为零,dp[i-1][1]也不会影响dp[i]的推导。

dp[0][2] 表示第0天结束时手上没有股票,且不在冷冻期,说明白天没有卖股票,dp[0][2]=0;

所以初始化为,

dp[0][0]=-prices[0]; dp[0][1]=0; dp[0][2]=0;

填表顺序

从左往右填写,一次性填写三个状态

返回值

返回最后一天中,三个状态的最大值,即

return fmax(dp[n-1][0],fmax(dp[n-1][1],dp[n-1][2]));

代码实现

 
int maxProfit(int* prices, int pricesSize) {
    int n=pricesSize;
    int dp[n][3];//0:有票   1:无票且在冷冻期   2:无票且不在冷冻期

    dp[0][0]=-prices[0];
    dp[0][1]=0;
    dp[0][2]=0;

    for(int i=1;i<n;i++){
        dp[i][0]=fmax(dp[i-1][0],dp[i-1][2]-prices[i]);
        dp[i][1]=fmax(dp[i-1][0]+prices[i],dp[i-1][2]);
        dp[i][2]=fmax(dp[i-1][1],dp[i-1][2]);
    }

    return fmax(dp[n-1][0],fmax(dp[n-1][1],dp[n-1][2]));

}

结尾

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

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

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

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

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

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

相关文章

fork函数详解【Linux】

fork函数详解【Linux】 fork函数的概念fork调用后的底层细节解释fork学习中的一些笔记和问题fork的写实拷贝深拷贝的策略 fork调用失败的原因 fork函数的概念 调用fork函数可以在已存在的进程中创建一个子进程&#xff0c;此时&#xff0c;新进程叫做子进程&#xff0c;原进程叫…

k8s二进制部署--部署高可用

连接上文 notready是因为没有网络&#xff0c;因此无法创建pod k8s的CNI网络插件模式 1.pod内部&#xff0c;容器与容器之间的通信。 在同一个pod中的容器共享资源和网络&#xff0c;使用同一个网络命名空间。 2.同一个node节点之内&#xff0c;不同pod之间的通信。 每个pod都…

element ui Checkbox 多选框组件 lable不支持Object类型的值的问题

浅浅记录一下&#xff0c;遇到这个问题的心理路程吧&#xff0c;首先我遇到的问题是多选框的值回显不打对勾&#xff0c;&#xff08;例如&#xff1a;你新增的时候多选&#xff0c;然后点击编辑的时候选过的值没有被勾选&#xff0c;其实是被勾选上了&#xff0c;但是没有显示…

Linux操作系统——进程(六) 进程地址空间

进程地址空间 C/C程序员一般将我们所写的程序看成如下这种结构&#xff1a; 我们所写的程序通过编译编译之后就可以以这样的方式进行分布. 我们先通过编写一段C语言代码来进行验证&#xff1a; 运行结果&#xff1a; 我们可以看出来上述地址遵循的就是我们上面画的一种结构。…

【附视频解析】Jmeter接口之间关联调用(获取上一个接口的返回值作为下一个接口的请求参数)

正则表达式&#xff1a; 具体如何操作&#xff1a; 1. 草稿保存&#xff0c; 此请求的响应数据的id 为发布总结的请求参数draft_id 2. 草稿保存的响应数据 3.在草稿保存的请求中&#xff0c;添加后置处理器- 正则表达式提取器&#xff0c; 提取响应数据的id信息 4. 发布总结请…

Java 缓存中间件

Java 缓存中间件 关键词&#xff1a;Spring Cache、J2Cache、JetCache 一 、JSR 107 JSR107 中制订了 Java 缓存的规范。 因此&#xff0c;在很多缓存框架、缓存库中&#xff0c;其 API 都参考了 JSR 107 规范。 img Java Caching 定义了 5 个核心接口 CachingProvider - 定义…

私有部署ELK,搭建自己的日志中心(三)-- Logstash的安装与使用

一、部署ELK 上文把采集端filebeat如何使用介绍完&#xff0c;现在随着数据的链路&#xff0c;继续~~ 同样&#xff0c;使用docker-compose部署&#xff1a; version: "3" services:elasticsearch:container_name: elasticsearchimage: elastic/elasticsearch:7.9…

Kafka学习笔记1(千峰教育)

Kafka学习笔记1&#xff08;千峰教育&#xff09; 一、为什么使用消息队列1.使用同步的通信方式来解决多个服务之间的通信2.使用异步的通信方式 二、消息队列的流派1.有broker2.无broker 三、Kafka的基本知识1.Kafk2a的安装2.Kafka中的一些基本概念3.创建topic4.发送消息5.消费…

sonarqube安装踩坑记录

如果用java1.8和mysql&#xff0c;则sonarqube版本不能超过7.8&#xff0c;看这里。 sonarqube7.8安装包地址&#xff1a; https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-7.8.zip 安装步骤&#xff1a; 1、下载sonarqube安装包 wget https://binari…

【PowerMockito:编写单元测试过程中采用when打桩失效的问题】

问题描述 正如上图所示&#xff0c;采用when打桩了&#xff0c;但是&#xff0c;实际执行的时候还是返回null。 解决方案 打桩时直接用any() 但是这样可能出现一个mybatisplus的异常&#xff0c;所以在测试类中需要加入以下代码片段&#xff1a; Beforepublic void setUp() …

软件测试/测试开发丨Pytest 参数化用例

参数化 通过参数的方式传递数据&#xff0c;从而实现数据和脚本分离。并且可以实现用例的重复生成与执行。 参数化应用场景 测试登录场景 测试登录成功&#xff0c;登录失败(账号错误&#xff0c;密码错误)创建多种账号: 中⽂文账号&#xff0c;英⽂文账号 普通测试用例方法 …

nginx 1.14.0引入自己使用C语言写的模块

参考的一篇博客《为NGINX编译第三方动态模块》 源码的地址&#xff1a;https://github.com/perusio/nginx-hello-world-module/tree/master。 源码被我放到系统中/root/nginx-hello-world-module目录下&#xff0c;ls /root/nginx-hello-world-module可以看一下源码里边的内容。…

C++初阶(十七)模板进阶

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、非类型模板参数二、模板的特化1、概念2、函数模板特化3、类模板特化1、全特化2、偏特化 三…

【tcp】TCP CLOSE_WAIT问题分析与定位

一、问题背景 某日&#xff0c;运维突然在群里突然丢出告警信息&#xff1a; 对象类型&#xff1a;主机 检测规则&#xff1a;NET.TCP.CLOSE.WAIT 告警内容&#xff1a;CLOSE_WAIT状态的TCP连接数大于500 ....image.png 上面告警信息已经说的很明白&#xff0c;CLOSE_WAIT状…

Ruff物联网数采网关助力工业企业数字化转型,降本增效

如今&#xff0c;随着工厂数字化转型进程的加速&#xff0c;越来越多的企业对于设备数据感知层及传输层的应用越来越重视&#xff0c;因此工业数采网关也走进了很多人的视野&#xff0c;在工厂数字化转型中扮演着关键角色。 物联网数据采集网关能将各种传感器、执行器等设备连…

主浏览器优化之路1——你现在在用的是什么浏览器?Edge?谷歌?火狐?360!?

上一世&#xff0c;我的浏览器之路 引言为什么要用两个浏览器为什么一定要放弃火狐结尾给大家一个猜数字小游戏&#xff08;测运气&#xff09; 引言 小时候&#xff0c;我一开始上网的浏览器是2345王牌浏览器吧&#xff0c; 因为上面集成了很多网站&#xff0c;我记得上面有7…

五轴机床测头:高精度曲面检测的得力工具

五轴机床测头广泛应用于制造业中的高精度加工领域。它能够准确、快速地检测出曲面的形状、尺寸和特征&#xff0c;为生产过程中的质量控制提供了重要支持。 五轴机床测头是一款具有3维5向探测功能的红外触发机床测头&#xff0c;广泛应用于 3 轴、5 轴加工中心&#xff0c;以及…

【Bootstrap学习 day1】

Bootstrap5 网格的基本结构 等宽响应式列 Bootstrap 5 网格系统有6个类&#xff1a; .col-针对所有设备 col-sm平板-屏幕宽度等于或大于576px。 .col-md-桌面显示器&#xff0c;屏幕宽度等于或大于768px col-lg大桌面显示器&#xff0c;屏幕宽度等于或大于992px col-xl特大桌面…

百度CTO王海峰:文心一言用户规模破1亿

▶ 写在前面▶ 飞桨开发者已达1070万▶ 文心一言用户规模破亿&#xff0c;日提问量快速增长 ▶ 写在前面 “文心一言用户规模突破 1 亿。”12 月 28日&#xff0c;百度首席技术官、深度学习技术及应用国家工程研究中心主任王海峰在第十届 WAVE SUMMIT 深度学习开发者大会上宣布…

事务的简介

一、什么是事务 事务是一组数据库的操作序列&#xff0c;包含一个或多个sql操作命令&#xff08;增删改&#xff09;&#xff0c;事务将所有的操作命令看做一个不可分割的整体&#xff0c;向数据库系统提交或撤销操作&#xff0c;所有操作要么执行要么不执行。 ●事务是一种机…