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

目录

引入:

例题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.mzph.cn/news/712485.shtml

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

相关文章

深度学习 精选笔记(8)梯度消失和梯度爆炸

学习参考&#xff1a; 动手学深度学习2.0Deep-Learning-with-TensorFlow-bookpytorchlightning ①如有冒犯、请联系侵删。 ②已写完的笔记文章会不定时一直修订修改(删、改、增)&#xff0c;以达到集多方教程的精华于一文的目的。 ③非常推荐上面&#xff08;学习参考&#x…

带你快速初步了解Python列表

1.列表 列表主要是用来存储多个数据&#xff0c;是有序的集合 2.创建列表 """ 语法&#xff1a;变量名 [数据1,数据2,数据3......] 注意&#xff1a;列表中的数据类型可以是各种不同的数据类型 """ 创建空列表 list1 [] print(list1) …

Gitlab: 私有化部署

目录 1. 说明 2. 资源要求 3. 安装 4. 配置实践 4.1 服务器 4.2 人员与项目 4.2 部署准备 4.2.1 访问变量及用户账号设置 4.2.2 Runner设置 4.2.3 要点 5. 应用项目 CI/CD 6. 参考 1. 说明 gitlab是一个强大且免费的代码管理/部署工具&#xff0c;能统一集成代码仓…

AngularJS入门

1. AngularJS简介 AngularJS是一个JavaScript框架,用js编写的库 <script src="https://cdn.staticfile.org/angular.js/1.4.6/angular.min.js"></script> <!-- 放在<body> 元素的底部。提高网页加载速度 -->1.1. AngularJS 扩展了 HTML …

Freesia项目目录结构

目录结构 前端目录&#xff1a; &#xff08;目录结构来自layui-vue-admin&#xff09; src文件下 api&#xff08;前端请求后端服务的路由&#xff09;assert&#xff08;一些内置或必要的资源文件&#xff09;layouts&#xff08;全局框架样式组件&#xff09;router&…

Unity(第十九部)射线

在Unity中&#xff0c;射线检测通常用于碰撞检测&#xff0c;比如&#xff1a;在游戏中&#xff0c;开枪射击时&#xff0c;需要判断击中的物体、子弹击中的位置&#xff1b;用鼠标来控制物体的移动&#xff1b;用鼠标拾取某个物体。 射线&#xff0c;顾名思义&#xff0c;在数…

【转载】深度学习笔记——详解损失函数

原文链接: https://blog.csdn.net/weixin_53765658/article/details/136360033 CSDN账号: Purepisces github账号: purepisces 希望大家可以Star Machine Learning Blog https://github.com/purepisces/Wenqing-Machine_Learning_Blog 损失函数 根据您使用的神经网络类型和数…

第四十七回 一丈青单捉王矮虎 宋公明二打祝家庄-强大而灵活的python装饰器

四面全是埋伏&#xff0c;宋江和众人一直绕圈跑不出去。正在慌乱之时&#xff0c;石秀及时赶到&#xff0c;教大家碰到白杨树就转弯走。走了一段时间&#xff0c;发现围的人越来越多&#xff0c;原来祝家庄以灯笼指挥号令。花荣一箭射下来红灯龙&#xff0c;伏兵自己就乱起来了…

Northwestern University-844计算机科学与技术/软件工程-复试注意事项【考研复习】

本文提到的西北大学是位于密歇根湖泊畔的西北大学。西北大学&#xff08;英语&#xff1a;Northwestern University&#xff0c;简称&#xff1a;NU&#xff09;是美国的一所著名私立研究型大学。它由九人于1851年创立&#xff0c;目标是建立一所为西北领地地区的人服务的大学。…

【力扣白嫖日记】550.游戏玩法分析IV

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 550.游戏玩法分析IV 表&#xff1a;Activity 列名类型player_idintdevice_idintevent_datedategames_played…

从 iOS 设备恢复数据的 20 个iOS 数据恢复工具

作为 iPhone、iPad 或 iPod 用户&#xff0c;您可能普遍担心自己可能会丢失存储在珍贵 iOS 设备中的所有宝贵数据。数据丢失的原因多种多样&#xff0c;这里列出了一些常见原因&#xff1a; 1. iOS 软件更新 2. 恢复出厂设置 3. 越狱 4. 误操作删除数据 5. iOS 设备崩溃 …

C++笔记(五)--- 虚函数(virtual)

目录 虚函数介绍 虚函数、覆盖和重载区别 虚函数介绍 C的虚函数是多态性的表现 1.构造函数不能为虚函数2.子类继承时虚函数仍为虚函数3.虚函数类外实现时&#xff0c;不需要加virtual4.有虚函数的类&#xff0c;析构函数一定要写成虚函数&#xff08;否则可能会造成内存泄漏&…

【代码随想录python笔记整理】第十六课 · 出现频率最高的字母

前言:本笔记仅仅只是对内容的整理和自行消化,并不是完整内容,如有侵权,联系立删。 一、哈希表初步 在之前的学习中,我们使用数组、字符串、链表等等,假如需要找到某个节点,则都要从头开始,逐一比较,直到找到为止。为了能够直接通过要查找的记录找到其存储位置,我们选…

设备像素、css像素、设备独立像素、dpr、ppi 之间的区别

设备像素、CSS 像素、设备独立像素 (DIP)、设备像素比 (DPR) 和每英寸像素密度 (PPI) 是与屏幕分辨率和显示质量相关的概念。它们之间的区别如下&#xff1a; 设备像素&#xff1a;设备像素是物理屏幕上的最小可见单元&#xff0c;用于实际渲染图像或文本。它表示硬件像素点的数…

、JMETER与它的组件们

os进程取样器 这个取样器可以让jmeter直接调用python写的测试数据 这样就可以调用python写的测试数据给到jmeter进行调用 注意&#xff1a;1建议python返回转json格式dumps一下&#xff1b;2py文件中需要把结果打印出来&#xff0c;可以不用函数直接编写 传到jmeter之后可以用…

你真的了解C语言中的【柔性数组】吗~

柔性数组 1. 什么是柔性数组2. 柔性数组的特点3. 柔性数组的使用4. 柔性数组的优势 1. 什么是柔性数组 也许你从来没有听说过柔性数组这个概念&#xff0c;但是它确实是存在的。 C99中&#xff0c;结构体中的最后⼀个元素允许是未知大小的数组&#xff0c;这就叫做柔性数组成员…

MyBatis 学习(五)之 高级映射

目录 1 association 和 collection 介绍 2 案例分析 3 一对一关联和一对多关联 4 参考文档 1 association 和 collection 介绍 在之前的 SQL 映射文件中提及了 resultMap 元素的 association 和 collection 标签&#xff0c;这两个标签是用来关联查询的&#xff0c;它们的属…

算法--时空复杂度分析以及各个数据量对应的可使用的算法(C++;1s内)

这里写目录标题 由数据范围反推算法时间复杂度以及算法内容分析时间复杂度看循环实例1实例2 固定时间复杂度快排和归并排序二分高精度算法双指针算法单链表插入删除操作栈和队列的操作单调栈和单调队列KMPTire并查集堆哈希表BFS、DFS图的深度优先、宽度优先遍历dijkstra算法朴素…

题目 1037: [编程入门]宏定义的练习

问题描述&#xff1a; 输入两个整数&#xff0c;求他们相除的余数。用带参的宏来实现&#xff0c;编程序。 样例输入&#xff1a; 3 2 样例输出&#xff1a; 1 代码分析&#xff1a; 这段代码实现了输入两个整数&#xff0c;然后使用带参数的宏计算它们相除的余数&…

「MySQL」深入理解MySQL中常用的SQL函数

「MySQL」深入理解MySQL中常用的SQL函数 窗口函数参考文章1. COALESCE 函数2. USING 函数3. LEAD 函数4. interval 函数5. INSTR 函数6. substring_index 函数7. LENGTH 函数和 CHAR_LENGTH 函数 窗口函数参考文章 SQL窗口函数 1. COALESCE 函数 COALESCE 函数的作用是从一…