《多状态DP:状态设计与状态转移方程速成指南》​

article/2025/6/26 10:57:07

1.按摩师

题目链接:面试题 17.16. 按摩师 - 力扣(LeetCode)

题目描述:从一个预约请求队列中,找出一个总预约时间最长的预约集合,不能选择相邻位置的预约

算法讲解:动态规划

1.状态表示:经验+题目要求

经验就是以i位置为起点或者以i位置为结尾,什么什么

在这道题中,根据题目要求和经验,dp[i]的状态表示为:

dp[i]表示:选择到i位置时,此时的最长预约时长

此时还可以继续细分状态表示,由于既可以选择第i个位置的预约,也可以不选择第i个位置的预约,此时就会有两种情况

f[i]表示选择到第i个位置时,选择第i个位置的预约时的最长预约总时长

g[i]表示选择到第i个位置时,不选择第i个位置的预约时的最长预约总时长 

2.状态转移方程 

第一种情况:选择第i个位置的预约 

此时,由于选择了第i个位置的预约,所以就不能再选择第i-1个位置上的预约,此时要求f[i]的话,首先要知道不选择第i-1个预约时的最长预约总时长, 根据细分的状态表示,可以用g[i-1]表示不选择第i-1个预约时的最长预约总时长,所以此时:f[i]=g[i-1]+nums[i]

第二情况:不选择第i个位置的预约

此时,由于不选择第i个位置的预约,所以在第i-1个位置的预约选择上,可以选择第i-1个位置上的预约,也可以不选择第i-1个位置上的预约,所以此时计算g[i]也会有两种情况

计算g[i]的第一种情况:选择第i-1个预约

此时计算g[i]时,就要知道选择到第i-1个预约时,选择第i-1个位置的预约时的最长总预约时长,根据状态表示,可以用f[i-1]来表示选择到第i-1个预约时,选择第i-1个位置的预约时的最长总预约时长,此时g[i]=f[i-1]

计算g[i]的第二种情况:不选择第i-1个预约

此时计算g[i]时,就要知道选择到第i-1个预约时,不选择第i-1个位置的预约时的最长总预约时长,根据状态表示,可以用g[i-1]来表示选择到第i-1个预约时,选择第i-1个位置的预约时的最长总预约时长,此时g[i]=g[i-1]

由于要计算最长的总预约时长,所以最终g[i]=max(f[i-1],g[i-1])

3.初始化

要初始化f[0]和g[0],根据状态表示,将f[0]初始化为nums[0]接口,将g[0]初始化为0

4.填表顺序

从左往右同时填f表和g表即可

5.返回值

返回max(f[n-1],g[n-1])即可 

代码实现:

class Solution {public int massage(int[] nums) {int n=nums.length;if(n==0) return 0;int[] f=new int[n];int[] g=new int[n];f[0]=nums[0];g[0]=0;for(int i=1;i<n;i++){f[i]=g[i-1]+nums[i];g[i]=Math.max(f[i-1],g[i-1]);}return Math.max(f[n-1],g[n-1]);}
}

2.打家劫舍 I 

题目链接:LCR 089. 打家劫舍 - 力扣(LeetCode) 

题目描述:一个小偷在不能偷相邻房屋的现金时,返回小偷偷到最后一个房屋时能偷到的最大金额。

算法原理:

1.状态表示:经验+题目要求

在这道题中,可以将偷到第i个位置时,此时的偷到的最大金额,但是偷到第i个位置时,小偷是可以选择偷第i个位置或者不偷第i个位置的,所以状态表示可以细分为两种

f[i]表示:偷到第i个位置时,偷nums[i],此时的最大金额

g[i]表示:偷到第i个位置时,不偷nums[i],此时的最大金额 

2.状态转移方程 

 推算状态转移方程时也会有两种情况

第一种情况:偷第i个位置的房屋

此时,由于偷了第i个位置时的房屋,所以就不能再去偷第i-1个位置上的房屋了,此时要推算f[i]的话,首先要知道不偷第i-1个位置的房屋时能偷到的最大金额,根据状态表示,可以用g[i-1]表示不偷第i-1个位置时能偷到的最大金额,所以此时:f[i]=g[i-1]+nums[i]

第二种情况: 不偷第i个位置的房屋

此时,由于不偷第i个位置的房屋,所以在偷第i-1个房屋的选择上,是可以选择偷第i-1个位置的房屋,也可以选择不偷第i-1个位置的房屋,所以,此时计算g[i]也会有两种情况

计算g[i]的第一种情况:偷第i-1个位置的房屋

此时,选择偷第i-1个位置的房屋,所以此时计算g[i]要知道偷到第i-1个位置时能得到的最大金额,根据状态表示,可以用f[i-1]来表示偷到第i-1个位置的房屋时得到的最大金额,所以,g[i]=f[i-1]

计算g[i]的第二种情况:不偷第i-1个位置的房屋

不选择偷第i-1个位置的房屋,所以此时计算g[i]要知道不偷到第i-1个位置时能得到的最大金额,根据状态表示,可以用g[i-1]来表示不偷到第i-1个位置的房屋时得到的最大金额,所以,g[i]=g[i-1]

由于要求的是最大金额,所以最终g[i]=Math.max(f[i-1],g[i-1]) 

3.初始化

根据状态表示,我们要去初始化f[0]和g[0],f[0]表示偷到第一个房屋时能够偷到的最大金额,此时就将f[0]初始化为nums[0],也就是f[0]=nums[0],同理,根据g[i]的状态表示,要将g[0]初始化为0 

4.填表顺序 

从左往右,同时填f表和g表 

5.返回值

返回max(f[n-1],g[n-1])即可 

代码实现:

class Solution {public int rob(int[] nums) {int n=nums.length;int[] f=new int[n];int[]g=new int[n];f[0]=nums[0];g[0]=0;for(int i=1;i<n;i++){f[i]=g[i-1]+nums[i];g[i]=Math.max(f[i-1],g[i-1]);}return Math.max(f[n-1],g[n-1]);}
}

3.打家劫舍 II ---转换为打家劫舍问题

题目链接:213. 打家劫舍 II - 力扣(LeetCode) 

这道题和打家劫舍 I 非常相似,只是在这道题中有一个不同点:第一个位置的房屋和最后一个位置的房屋也是相邻的,但小偷偷了第一个房屋,就不能盗窃最后一个房屋了,所以此时会有两种情况

第一种情况:偷第1个位置上的房屋

此时,如果偷了第1个位置上的房屋,那么第2个位置的房屋最后一个位置的房屋都不能偷了,所以此时,只要计算从第3个位置的房屋偷到倒数第二个位置的房屋时能偷到的最大金额(运用打家劫舍 I 的思路) ,然后再加上nums[0]的金额就是第一种情况的最大金额

第二种情况:不偷第1个位置的房屋

此时,如果不偷第1个位置上的房屋,那么此时计算从第2个位置的房屋都到最后一个房屋时的最大金额即可(运用打家劫舍 I 的思路) 

代码实现:

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;int n=nums.length;int[] f=new int[n];int[] g=new int[n];f[left]=nums[left];for(int i=left;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]);}
}//我写的版本
class Solution {public int rob(int[] nums) {int n=nums.length;if(n==1) return nums[0];if(n==2) return Math.max(nums[0],nums[1]);int[] f1=new int[n];int[] g1=new int[n];int[] f2=new int[n];int[] g2=new int[n];f1[2]=nums[2];for(int i=2;i<n-1;i++){f1[i]=g1[i-1]+nums[i];g1[i]=Math.max(f1[i-1],g1[i-1]);}int ret1=Math.max(f1[n-2],g1[n-2])+nums[0];f2[1]=nums[1];for(int i=1;i<n;i++){f2[i]=g2[i-1]+nums[i];g2[i]=Math.max(f2[i-1],g2[i-1]);}int ret2=Math.max(f2[n-1],g2[n-1]);return Math.max(ret1,ret2);} 
}

4.删除并获得点数---转换为打家劫舍问题 

题目链接:740. 删除并获得点数 - 力扣(LeetCode) 

 题目解析:

这道题依旧是打家劫舍类型的题目,根据题目描述,当选择x数字时,x+1数字和x-1数字是不能被选择的,就像打家劫舍问题中选择i位置的金额后就不能选择i+1位置的金额和i+1位置的金额。

如果在这道题中,nums=【1,2,3,4,5】,那么此时这道题就直接用打家劫舍的思路解决即可,但是nums有可能不是连续的情况,例如nums=【1,2,2,4,4,5,5,7,9,9,9】,但是可以将nums数组的数据映射到一个arr数组中,其中,在arr数组中,i表示nums[i]arr[i]表示 “i这个数字出现的总和”,此时数组的下标是有序到的,所以此时直接对arr数组进行一次打家劫舍就行了

算法讲解:

1.状态表示:

用f[i]表示:选到i位置时,arr[i]必选,此时能够获得的最大点数

用g[i]表示:选到i位置时,arr[i]不选,此时能够获得的最大点数

2.状态转移方程:

f[i]=g[i-1]+nums[i]g[i]=Math.max(f[i-1],g[i-1]) 

3.初始化:

f[0]=arr[0]g[0]=0 

 4.填表顺序

从左往右,同时填两个表即可

5.返回值

返回max(f[n-1],g[n-1])即可

代码实现:

class Solution {public int deleteAndEarn(int[] nums) {int n=nums.length;int max=nums[0];for(int x:nums){if(x>max) max=x;}int[] arr=new int[max+1];for(int i=0;i<n;i++)arr[nums[i]]+=nums[i];int[] f=new int[max+1];int[] g=new int[max+1];//初始化f[0]=arr[0];for(int i=1;i<max+1;i++){f[i]=g[i-1]+arr[i];g[i]=Math.max(f[i-1],g[i-1]);}return Math.max(f[max],g[max]);    }
}

5.粉刷房子 

题目链接:LCR 091. 粉刷房子 - 力扣(LeetCode) 

题目解析:相邻的房子不能涂相同的颜色,计算并返回涂刷完所有房子的最小费用

算法讲解: 

1.状态表示:经验+题目要求

这道题中,可以将 粉刷到第i个房子时,花费的最小费用 作为 状态表示,但是此时还可以细分状态表示,当涂刷到第i个房子时,是有红蓝绿三种颜色选择的,所以细分的状态表示:

dp[i][0]表示:粉刷到第i个房子时,选择刷红色,此时花费的最小费用 

dp[i][1]表示: 粉刷到第i个房子时,选择刷蓝色,此时花费的最小费用

dp[i][2]表示:粉刷到第i个房子时,选择刷绿色,此时花费的最小费用 

2.状态转移方程

由于有3种状态表示,所以推状态转移方程也会有3中情况,但是推一种情况就行了,另外两种情况推算的逻辑一样,下面,将以推算dp[i][0]为例,也就是以红色为例 

dp[i][0]表示将会第i个位置的房子刷成红色,此时花费的最小费用,如果想要求出dp[i][0],此时就必须要知道粉刷到第i-1个房子时花费的最小费用,此时因为第i个房子粉刷成红色了。所以此时第i-1个位置的房屋只会有粉刷蓝色或者绿色这两种情况,所以此时就要知道 粉刷到第i-1个房子并且将房子粉刷成蓝色的最小花费 或者 粉刷到第i-1个房子并且将房子粉刷成绿色的最小花费,由于要求的是最小花费,所以此时的状态转移方程:

dp[i][0]=Math.min(dp[i-1][1],dp[i-1][2])

同理可得:其他两种情况的状态转移方程为:

将第i个房子刷成蓝色:dp[i][1]=Math.min(dp[i-1][0],dp[i-1][2])

将第i个房子刷成绿色:dp[i][1]=Math.min(dp[i-1][0],dp[i-1][1])

3.初始化

初始化时,要进行dp[0][0]=cost[0][0],dp[1][1]=cost[1][0],dp[2][2]=cost[2][0]这些初始化的操作,为了方便初始化,可以在创建dp表时,多创建一列的空间,此时就有两个注意事项:

第一个注意事项:多出来的空间里填的值,要保证后面的填表正确,此时根据题目分析,将多出来的空间全部填0即可

第二个注意事项:注意下标的映射关系

4.填表顺序:

从左往右,同时填三个表即可

5.返回值

返回min(dp[n][0],dp[n][1],dp[n][2])即可

代码实现:

class Solution {public int minCost(int[][] cost) {//房子数量int n=cost.length;int[][] dp=new int[n+1][3];for(int i=1;i<n+1;i++){dp[i][0]=Math.min(dp[i-1][1],dp[i-1][2])+cost[i-1][0];dp[i][1]=Math.min(dp[i-1][0],dp[i-1][2])+cost[i-1][1];dp[i][2]=Math.min(dp[i-1][0],dp[i-1][1])+cost[i-1][2];}return Math.min(dp[n][0],Math.min(dp[n][1],dp[n][2]));}
}

http://www.hkcw.cn/article/jjNkmChySS.shtml

相关文章

Spring Cloud 开发入门:环境搭建与微服务项目实战(上)

一、开发环境搭建 1. JDK 安装与版本选择 版本选择解析 Java 是 Spring Cloud 微服务开发的基础&#xff0c;选择合适的 JDK 版本至关重要&#xff0c;特别是在框架兼容性和生产环境稳定性方面。 &#xff08;1&#xff09;主流 JDK 版本对比 版本发布年份支持状态特点简述J…

PINN for PDE(偏微分方程)3 - 正向问题 - Burgers’ equation

PINN for PDE(偏微分方程)3 - 正向问题 - Burgers’ equation 目录 PINN for PDE(偏微分方程)3 - 正向问题 - Burgers’ equation一、什么是PINN的正问题二、求解的实际例子 - Burgers’ equation2.1 Burgers方程2.2 无解析解解决办法 - 龙哥库达&#xff08;Runge-Kutta 4th O…

机器人夹爪的选型与ROS通讯——机器人抓取系统基础系列(六)

文章目录 前言一、夹爪的选型1.1 任务需求分析1.2 软体夹爪的选型 二、夹爪的ROS通讯2.1 夹爪的通信方式介绍2.2 串口助手测试2.3 ROS通讯节点实现 总结Reference: 前言 本文将介绍夹爪的选型方法和通讯方式。以鞋子这类操作对象为例&#xff0c;将详细阐述了对应的夹爪选型过…

003-Python-脚本学习-安装mysql数据库(CentOS7.9)

#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Name: Python-脚本学习-安装mysql数据库&#xff08;CentOS7.9&#xff09;.py # Author: songp-it # Date: 2024-03-21 # Description: 在CentOS 7.9上安装MySQL数据库import subprocess import sys import osdef disable_s…

数字孪生离心泵,精准复刻泵体运行

图扑数字孪生离心泵&#xff0c;以 1:1 高精度建模&#xff0c;逼真呈现离心泵结构。动态模拟内部水流与部件运转&#xff0c;实时反馈运行参数&#xff0c;助力运维人员精准掌握泵体状态&#xff0c;高效诊断故障&#xff0c;提升离心泵运行管理水平。

这几种“奇葩果” 买了就后悔!

这几种“奇葩果”买了就后悔。“拇指西瓜”,长得像西瓜的微缩板,但压根不是西瓜。吃起来也远没有西瓜的甘甜多汁。刺角瓜,也叫火参果。很多人因为奇异的外表而好奇购买,味道酸涩。虽然富含维生素C与膳食纤维,但价格却被商家炒得过于昂贵。露兜果,也叫野菠萝,外观形似菠萝…

王楚钦与女粉丝合影手立马放背后,妥妥的男德模范生!

王楚钦与女粉丝合影手立马放背后。王楚钦现身魏桥参加活动,现场人气爆棚,大批球迷慕名而来,只为近距离目睹偶像风采。难得的机会让合影环节热度高涨,王楚钦又成了人形打卡机,耐心地满足球迷的合影请求。遇到女性球迷合影时,王楚钦的手臂自动就藏了起来,保持得体的距离,…

Java开发工具——Arthas线上查询工具

摘要 本文主要介绍了 Java 开发工具 Arthas 的安装、启动及使用。包括本地安装与启动、容器安装与启动、基础命令与使用以及常用命令与使用等内容。通过 Arthas&#xff0c;开发者可以方便地对 Java 应用进行线上排错和监控&#xff0c;提高开发效率。 1. Arthas 本地安装与启…

5.RV1126-OPENCV 图形计算面积

一.图形面积、弧长计算介绍 前面我们已经把图形轮廓的检测、画框等功能讲解了一遍。这次主要结合轮廓检测的 API 去计算图形的面积&#xff0c;这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能&#xff0c;常用的 API 如 contourArea…

rabbitmq Topic交换机简介

1. Topic交换机 说明 尽管使用 direct 交换机改进了我们的系统&#xff0c;但是它仍然存在局限性——比方说我们的交换机绑定了多个不同的routingKey&#xff0c;在direct模式中虽然能做到有选择性地接收日志&#xff0c;但是它的选择性是单一的&#xff0c;就是说我的一条消息…

JavaSE 常见问题解析

最近正在复习Java八股&#xff0c;所以会将一些热门的八股问题&#xff0c;结合ai与自身理解写成博客便于记忆 本文将以以上问题作为基础 String 相关问题 String 底层数据类型&#xff1f; String 在 Java 9 之前底层使用 char[] 数组存储字符数据&#xff0c;Java 9 及以…

潜入水面:穿越“冰山”之旅,探寻Java鲜为人知的一面

“冰山”梗是一种网络现象&#xff0c;幽默而有时令人不安地展示了某个主题的知识或入门层次——从冰山之巅简单、广为人知的常识到只有最坚韧的老兵才能理解的黑暗、神秘深处。想象一座海洋上矗立的冰山&#xff1a;表面可见的部分只是开始&#xff0c;真正的魔法&#xff08;…

如何配置mvn镜像源为华为云

如何配置mvn镜像源为华为云 # 查找mvn 配置文件 mvn -X help:effective-settings | grep settings.xml# 配置mvn镜像源为华为云&#xff0c;/home/apache-maven-3.9.5/conf/settings.xml文件路径需要根据上一步中查询结果调整 cat > /home/apache-maven-3.9.5/conf/setting…

【DAY37】早停策略和模型权重的保存

内容来自浙大疏锦行python打卡训练营 浙大疏锦行 知识点&#xff1a; 过拟合的判断&#xff1a;测试集和训练集同步打印指标模型的保存和加载 仅保存权重保存权重和模型保存全部信息checkpoint&#xff0c;还包含训练状态 早停策略 作业&#xff1a; 对信贷数据集训练后保存权…

TASK OA 案例hook

TASK OA 案例hook 定义的状态 useRef & useForm ref使用&#xff1a; xxx 尽可能使用组件库antd内部提供的方法 两大稍微比较难的组件&#xff1a;table 和 form 服务器通信 使用async/await 不用想配套使用 try/catch 初次渲染拉取query。useEffect(..., []) 状态更新useE…

Kafka集成Flume/Spark/Flink(大数据)/SpringBoot

Kafka集成Flume Flume生产者 ③、安装Flume&#xff0c;上传apache-flume的压缩包.tar.gz到Linux系统的software&#xff0c;并解压到/opt/module目录下&#xff0c;并修改其名称为flume Flume消费者 Kafka集成Spark 生产者 object SparkKafkaProducer{def main(args:Array[S…

Linux指令:

我们今天来学习一下linux的一些相关的指令L&#xff1a; 1. 快速认识6~8个指令&#xff1a; 第一条&#xff1a;pwd pwd指令表示的是我当前在哪条路径下&#xff1b;我当前在哪里&#xff1b; 我们看这个第二句话&#xff0c;因为在windows环境下&#xff0c;当我们登录进入到…

网络攻防技术五:网络扫描技术

文章目录 一、网络扫描的基础概念二、主机发现三、端口扫描1、端口号2、端口扫描技术3、端口扫描隐秘策略 四、操作系统识别五、漏洞扫描六、简答题1. 主机扫描的目的是什么&#xff1f;请简述主机扫描方法。2. 端口扫描的目的是什么&#xff1f;请简述端口扫描方法及扫描策略。…

win32相关(虚拟内存和物理内存)

虚拟内存和物理内存 在win32操作系统下&#xff0c;每个进程都有它自己独立的4GB空间&#xff0c;是window给它分配的一个虚拟空间&#xff0c;并不是真正的物理空间&#xff0c;这4GB空间中&#xff0c;分为高2G和低2G&#xff0c;高2G是应用程序的&#xff0c;低2G空间是给内…

00后新人“整顿”婚礼 简简单单更实在!

00后新人“整顿”婚礼简简单单更实在!婚礼当天,宾客们刚坐下,新郎新娘就手拉手走上台。新郎咧嘴一笑:“感谢各位来捧场,我俩今天正式领证了!”新娘接茬:“废话不多说,大家吃好喝好,菜不够再加,吃不完打包带走!”台下瞬间爆发出欢呼声,这场婚礼从开始到宣布开席,总…