动态规划课堂3-----简单多状态问题(买卖股票最佳时机)

news/发布时间2024/9/20 8:12:52

目录

引入:

例题1:按摩师(打家劫舍I)

例题2:打家劫舍II

例题3:删除并获得点数

例题4:粉刷房子

例题5:买卖股票的最佳时机含冷冻

结语:


引入:

相信看到这里的友友们对动态规划已经有了一定的了解,下面我将介绍动态规划的简单多状态dp问题。所谓多状态就是在一步下有不同的情况要区分(例如买股票,今天可以分为买还是不买的多两种情况)。由于算法只讲知识点是远远不够的故下面我会在例题中穿插知识点帮助理解。动态规划一般的解题步骤还是1. 状态表示,2.状态转移方程,3.初始化,4.填表顺序,5.返回值。在写代码时一定要把这5步考虑清楚再写代码。

例题1:按摩师(打家劫舍I)

链接:按摩师

题目简介:

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

解法(动态规划):

1. 状态表示:

对于简单的线性dp ,我们可以用「经验+题⽬要求」来定义状态表示例如:

(1)以某个位置为结尾的最小花费。

(2)以某个位置为起点到终点的最小花费。

故我们这题采用常用的方式:dp[i] 表⽰:选择到i 位置时,此时的最⻓预约时⻓。由于我们这个题在i 位置的时候,会⾯临选择或者不选择两种抉择,所依赖的状态需要细分。

下面之所以用f和g是因为高中我们所学的f(x)和g(x),可以换成其他的(但最好用f和g就当作是不成文的规定,这样代码的可读性会更高)。

(1)f[i] 表示:选择到i 位置时, nums[i] 必选,此时的最⻓预约时⻓。

(2)g[i]表示:选择到i 位置时, nums[i] 不选,此时的最⻓预约时⻓。

2.状态转移方程

因为状态表示定义了两个,因此我们的状态转移⽅程也要分析两个:

对于f[i] :

如果nums[i] 必选,那么我们仅需知道i - 1 位置在不选的情况下的最⻓预约时⻓, 然后加上nums[i] 即可,因此f[i] = g[i - 1] + nums[i] 。

对于g[i] :

如果nums[i] 不选,那么i - 1 位置上选或者不选都可以。因此,我们需要知道i - 1 位置上选或者不选两种情况下的最⻓时⻓,因此g[i] = max(f[i - 1], g[i - 1]) 。

在状态转移方程这里可以画图来帮助我们理解

3.初始化

这道题的初始化⽐较简单,因此⽆需加辅助节点(前两篇文章已解释),仅需初始化f[0] = nums[0], g[0] = 0 即可。

4.填表顺序

根据「状态转移⽅程」得「从左往右,两个表⼀起填」。

5.返回值

根据「状态表示」,应该返回max(f[n - 1], g[n - 1]) 。

代码实现如下:

class Solution {public int massage(int[] nums) {//1.创建dp表//2.初始化//3.填表//4.返回值int n = nums.length;if(n == 0){return 0;}int[] f = new int[n];//选择int[] g = new int[n];//不选择f[0] = nums[0];for(int i = 1;i < n;i++){f[i] = g[i - 1] + nums[i];g[i] = Math.max(g[i - 1],f[i - 1]);}return Math.max(g[n -1],f[n - 1]);}
}

时间复杂度:O(n)

空间复杂度:O(n)

例题2:打家劫舍II

链接:打家劫舍II

题目简介:

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

 解法(动态规划):

通过阅读题目友友可以发现这题和例题一是差不多的,唯一不同就是例题二加了一个末尾和开头的限制。上⼀个问题是⼀个「单排」的模式,这⼀个问题是⼀个「环形」的模式,也就是⾸尾是相连的。但是我们可以将「环形」问题转化为「两个单排」问题:

(1)偷第⼀个房屋时的最大⾦额x ,此时不能偷最后⼀个房⼦,因此就是偷[0, n - 2] 区间的房⼦。

(2)不偷第⼀个房屋时的最大⾦额y ,此时可以偷最后⼀个房⼦,因此就是偷[1, n - 1] 区 间的房⼦。

两种情况下的「最大值」,就是最终的结果。

代码如下:

下面的rob1方法就是例题一。

class Solution {public int rob(int[] nums) {int n = nums.length;return Math.max(nums[0] + rob1(nums,2,n - 2), rob1(nums,1,n - 1));}public int rob1(int[] nums,int left,int right){if(left > right){return 0;}//1.创建dp表//2.初始化//3.填表//4.返回值int n = nums.length;int[] f = new int[n];int[] g = new int[n];f[left] = nums[left];for(int i = left + 1;i <= right;i++){f[i] = g[i - 1] + nums[i];g[i] = Math.max(g[i - 1],f[i - 1]);}return Math.max(f[right],g[right]);}
}

时间复杂度:O(n) 

空间复杂度:O(n)

例题3:删除并获得点数

链接:删除并获得点数

题目简介:

给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

解法(动态规划):

其实这道题依旧是「打家劫舍I」问题的变型。 我们注意到题⽬描述,选择x 数字的时候, x - 1 与x + 1 是不能被选择的。像不像「打家劫舍」问题中,选择i 位置的⾦额之后,就不能选择i - 1 位置(数组中)以及i + 1 位置的⾦额呢~ 因此,我们可以创建⼀个⼤⼩为10001 (根据题⽬的数据范围)的hash 数组,将nums 数 组中每⼀个元素x ,累加到hash 数组下标为x 的位置处,然后在hash数组上来⼀次「打家劫舍」即可。

过程如下图:

弧线表示0到i - 1之间能获得的最大点数。

代码如下:

class Solution {public int deleteAndEarn(int[] nums) {//1.创建dp表//2.初始化//3.填表//4.返回值int n = 10001;int[] arr = new int[n];for(int x:nums){arr[x] += x;}int[] f = new int[n];int[] g = new int[n];f[0] = arr[0];for(int i = 1;i < n;i++){f[i] = g[i - 1] + arr[i];g[i] = Math.max(f[i - 1],g[i - 1]);}return Math.max(f[n - 1],g[n - 1]);}
}

时间复杂度:O(n) 

空间复杂度:O(n)

例题4:粉刷房子

链接:粉刷房⼦

题目简介:

 解法(动态规划):

 1. 状态表示:

对于线性dp ,我们可以⽤「经验+题⽬要求」来定义状态表⽰:但是我们这个题在i 位置的时候,会⾯临「红」「蓝」「绿」三种抉择,所依赖的状态需要细分:

(1)dp[i][0] 表⽰:粉刷到i 位置的时候,最后⼀个位置粉刷上「红⾊」,此时的最⼩花费。 

(2)dp[i][1] 表⽰:粉刷到i 位置的时候,最后⼀个位置粉刷上「蓝⾊」,此时的最⼩花费。

(3)dp[i][2] 表⽰:粉刷到i 位置的时候,最后⼀个位置粉刷上「绿⾊」,此时的最⼩花费。

 2.状态转移方程

因为状态表⽰定义了三个,因此我们的状态转移⽅程也要分析三个:

对于dp[i][0] :

如果第i个位置粉刷上「红⾊」,那么i-1位置上可以是「蓝⾊」或者「绿⾊」。因此我们 需要知道粉刷到i-1位置上的时候,粉刷上「蓝⾊」或者「绿⾊」的最⼩花费,然后加上i 位置的花费即可。于是状态转移⽅程为: dp[i][0] = min(dp[i - 1][1], dp[i - 1][2]) + costs[i - 1][0] ;

同理,我们可以推导出另外两个状态转移⽅程为:

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

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

 3.初始化

采用最前⾯加上⼀个「辅助结点」。

注意点:(1)辅助结点⾥⾯的值要「保证后续填表是正确的」(2)「下标的映射关系」。

 4.填表顺序

 5.返回值

根据「状态表⽰」,应该返回最后⼀个位置粉刷上三种颜⾊情况下的最⼩值,因此需要返回: min(dp[n][0], min(dp[n][1], dp[n][2])) 。

代码如下:

class Solution {public int minCost(int[][] costs) {//1.创建dp表//2.初始化//3.填表//4.返回值int n = costs.length;int[][] dp = new int[n + 1][3];for(int i = 1;i <= n;i++){dp[i][0] = Math.min(dp[i - 1][1],dp[i - 1][2]) + costs[i - 1][0];dp[i][1] = Math.min(dp[i - 1][0],dp[i - 1][2]) + costs[i - 1][1];dp[i][2] = Math.min(dp[i - 1][1],dp[i - 1][0]) + costs[i - 1][2];}return Math.min(dp[n][0],Math.min(dp[n][1],dp[n][2]));}
}

时间复杂度:O(n) 

空间复杂度:O(n)

例题5:买卖股票的最佳时机含冷冻

链接:买卖股票的最佳时机含冷冻期

题目简介:

  解法(动态规划):

 1. 状态表示:

这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:由于有「买⼊」「可交易」「冷冻期」三个状态,因此我们可以选择⽤三个数组,其中:

(1)dp[i][0] 表⽰:第i 天结束后,处于「买⼊」状态,此时的最⼤利润。

(2)dp[i][1] 表⽰:第i 天结束后,处于「可交易」状态,此时的最⼤利润。

(3)dp[i][2] 表⽰:第i 天结束后,处于「冷冻期」状态,此时的最⼤利润。

我们要谨记规则:

(1)处于「买⼊」状态的时候,我们现在有股票,此时不能买股票,只能继续持有股票,或者卖 出股票;

(2)处于「买⼊」状态的时候,我们现在有股票,此时不能买股票,只能继续持有股票,或者卖 出股票;

 2.状态转移方程

确定状态表示后,我们可以画图来帮助我们理解根据题目要求我们可以画图下图,并推出状态转移方程。

 3.初始化

三种状态都会⽤到前⼀个位置的值,因此需要初始化每⼀⾏的第⼀个位置:

dp[0][0] :此时要想处于「买⼊」状态,必须把第⼀天的股票买了,因此dp[0][0] = - prices[0] ; dp[0][1] :啥也不⽤⼲即可,因此dp[0][1] = 0 ;

dp[0][2] :手上没有股票,买⼀下卖⼀下就处于冷冻期,此时收益为0 ,因此 dp[0][2] = 0 。

4.填表顺序

根据「状态表⽰」,我们要三个表⼀起填,每⼀个表「从左往右」。

5.返回值

应该返回「卖出状态」下的最⼤值,因此应该返回max(dp[n - 1][1], dp[n - 1] [2]) 。

代码如下:

class Solution {public int maxProfit(int[] prices) {//1.创建dp表//2.初始化//3.填表//4.返回值int n = prices.length;int[][] dp = new int[n][3];dp[0][0] = -prices[0];for(int i = 1;i < n;i++){dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][2]);dp[i][2] = dp[i - 1][0] + prices[i];}return Math.max(dp[n - 1][1],dp[n - 1][2]);}
}

时间复杂度:O(n) 

空间复杂度:O(n)

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

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

如若内容造成侵权/违法违规/事实不符,请联系编程老四网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

vue2使用fabric实现简单画图demo,完成批阅功能

这个功能主要实现批阅的&#xff0c;修改上传的图片&#xff0c;标记内容 看看效果图 主要是根据fabric这个库来进行完成的 下载包 npm i fabric 组件内注册使用 import { fabric } from fabric; 步骤 body中 <divv-if"imgs.length"class"canvas-wrap…

C++之数组

1&#xff0c;概述 所谓数组&#xff0c;就是一个集合&#xff0c;里面存放了相同类型的数据元素 特点1&#xff1a;数组中没干过数据元素都是相同的数据类型 特点2&#xff1a;数组都是连续存放位置组成的 2&#xff0c;一维数组 2.1 一维数组的定义 一维数组定义有三种…

杭电OJ 2018 母牛的故事 C++

思路&#xff1a;有点像是斐波那契数列 #include <iostream> #include <vector> using namespace std; int main() { int n; vector<int> num(56); num[1] 1; num[2] 2; num[3] 3; num[4] 4; for (int i 5; i < num.size(); i) { …

MySQL:错误ERROR 1045 (28000)详解

1.问题说明 有时候我们登录Mysql输入密码的时候&#xff0c;会出现这种情况&#xff1a; mysql -u root -p Enter Password > ‘密码’ 错误&#xff1a;ERROR 1045 (28000): Access denied for user ‘root’‘localhost’ (using password: YES) 或者&#xff1a;错误…

VR元宇宙的概念|VR体验店加盟|虚拟现实设备销售

VR元宇宙是一个结合了虚拟现实&#xff08;Virtual Reality&#xff09;和增强现实&#xff08;Augmented Reality&#xff09;等技术的概念&#xff0c;代表着一个虚拟的多维度世界。它是一个由数字化的空间构成的虚拟环境&#xff0c;可以通过虚拟现实设备进行交互和探索。 元…

数据结构从入门到精通——算法的时间复杂度和空间复杂度

算法的时间复杂度和空间复杂度 前言一、算法效率1.1 如何衡量一个算法的好坏1.2 算法的复杂度 二、时间复杂度2.1 时间复杂度的概念2.2 大O的渐进表示法2.3常见时间复杂度计算举例2.4等差数列计算公式2.5等比数列计算方法 三、空间复杂度四、 常见复杂度对比五、 复杂度的oj练习…

RabbitMQ安装

⭐ 作者简介&#xff1a;码上言 ⭐ 代表教程&#xff1a;Spring Boot vue-element 开发个人博客项目实战教程 ⭐专栏内容&#xff1a;个人博客系统 ⭐我的文档网站&#xff1a;http://xyhwh-nav.cn/ 文章目录 RabbitMQ安装下载安装Rabbitmq-server RabbitMQ安装 下载 官…

Ansible stat模块 stat模块 – 检索文件或文件系统状态

目录 语法判断一个不存在的文件 stat模块 – 检索文件或文件系统状态 语法 这里查看/tmp/index.html 这个文件在不 ansible slave -m stat -a path/tmp/index.html判断一个不存在的文件 ansible slave -m stat -a path/tmp/index111.html返回false 说明文件不存在 本章结束…

4.5.CVAT——视频标注的详细步骤

文章目录 1. 跟踪模式&#xff08;基础&#xff09;2. 跟踪模式&#xff08;高级&#xff09;3. 带多边形的轨迹模式 追踪模式Track mode &#xff08;视频标注使用&#xff09;——类似pr的动画效果 1. 跟踪模式&#xff08;基础&#xff09; 使用示例&#xff1a; 为一系列…

继电器测试中需要注意的安全事项有哪些?

继电器广泛应用于电气控制系统中的开关元件&#xff0c;其主要功能是在输入信号的控制下实现输出电路的断开或闭合。在继电器测试过程中&#xff0c;为了确保测试的准确性和安全性&#xff0c;需要遵循一定的安全事项。以下是在进行继电器测试时需要注意的安全事项&#xff1a;…

作业1-224——P1331 海战

思路 深搜的方式&#xff0c;让它只遍历矩形块&#xff0c;然后在下面的遍历中判断是否出现矩形块交叉&#xff0c;但是很难实现&#xff0c;然后发现可以通过在遍历过程中判断是否合法。 参考代码 #include<iostream> #include<cstdio> using namespace std; …

php 支持mssqlserver

系统不支持:sqlsrv 需要一下几个环节 1.准备检测php版本 查看 VC 版本 查看操作系统位数&#xff1a;X86(32位) 和X64 2.下载php的sqlserver库 extensionphp_sqlsrv_74_nts_x64.dll extensionphp_pdo_sqlsrv_74_nts_x64.dll extensionphp_sqlsrv_74_nts_x64 extensionphp_…

《C++进阶--10.多态》

目录 10. 多态 10.1 多态的基本概念 10.2 多态案例一-计算器类 10.3 纯虚函数和抽象类 10.4 多态案例二-制作饮品 10.5 虚析构和纯虚析构 10.6 多态案例三-电脑组装 10. 多态 10.1 多态的基本概念 多态是C面向对象三大特性之一 多态分为两类 静态多态: 函数重载 和 运算…

规范 Git 提交说明

Commit Message 的规范 Commit Message 的规范主要包括以下几个部分&#xff1a; Header&#xff1a;包括三个字段&#xff0c;分别是 type&#xff08;必需&#xff0c;用于说明 commit 的类别&#xff09;、scope&#xff08;必需&#xff0c;说明 commit 影响的范围&#…

高性能的key-value数据库Redis 介绍

Redis 是一个高性能的key-value数据库。 Redis是一个开源的键值存储系统&#xff0c;通常用于缓存和消息传递。它支持多种类型的数据结构&#xff0c;如字符串、列表、集合、散列表和有序集合等。Redis的特点是提供了高性能、灵活性和可伸缩性。 Redis的主要特点包括&#xff…

【嵌入式学习】网络编程day0229

一、思维导图 二、练习 TCP通信 服务器 #include <myhead.h> #define SER_IP "192.168.126.42" #define SER_PORT 8888 int main(int argc, const char *argv[]) {int wfd-1;//创建套接字if((wfdsocket(AF_INET,SOCK_STREAM,0))-1){perror("error"…

代码随想录Leetcode139. 单词拆分

题目&#xff1a; 代码(首刷看解析 2024年2月28日&#xff09;&#xff1a; class Solution { public:// 动态规划bool wordBreak(string s, vector<string>& wordDict) {int n s.size();// 初始化dp[i]vector<int> dp(n 1, false);dp[0] true;// 遍历 排列…

Linux——进程控制(一)进程的创建与退出

目录 一、进程创建 1.写时拷贝 2.创建多个进程 二、进程终止 1.main函数的返回值 2.bash中的$? 3.自定义退出码 4.C语言的错误码 5.错误码与退出码的区别 6.代码异常终止 7.exit函数 8.总结 一、进程创建 在之前&#xff0c;我们学过linux中的非常重要的函数——…

【网站项目】328学生就业管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

c++面试2

一 、 char类型数组转换成字符串的方法 使用 std::string 构造函数: 可以使用 std::string 类的构造函数直接将 C 风格字符串转换为 std::string 对象&#xff0c;例如&#xff1a;std::string str(buf);。 const char* buf "Hello, world!"; // C 风格字符串 std::…
推荐文章