【动态规划】打家劫舍类问题

一、按摩师

17.16. 按摩师

题目描述:

题目分析:

1、状态表示

每个预约都只会有两种选择,即选或不选。因此我们可以用

  • dpi0 表示不选择第 i 个预约时,最长的预约时长
  • dpi1 表示选择第 i 个预约时,最长的预约时长

2、状态转移方程

对于 dpi0

  • 如果我们选择了第 i 个预约,那么第 i-1 次预约就一定不会选择,这时我们只需要知道不选第 i-1 次预约时的最长预约时长即可,即 dpi-10 的值,再加上 numi 即可。可得递推公式就为:

dpi1=dpi-10+numsi

对于 dpi1

  • 如果我们不选择第 i 个预约,那么第 i-1 次预约就可以被选择,当然也可以不选,这时我们只需要知道选或不选第 i-1 次预约时分别的最长预约时长即可,即 dpi-10 与 dpi-10 的值,取这两个中的最大值即可。可得递推公式就为:

dpi0=Math.max(dpi-11,dpi-10)

3、初始化

为了避免 i-1 时越界,我们初始化dp表的长度为 n+1,然后从 i=1 开始遍历

要注意下标的映射关系,此时 numsi-1 表示nums中第 i 个元素

4、返回值

直接返回 Math.max(dpn0,dpn1),因为我们不确定最后一个选不选。

代码实现:

java 复制代码
class Solution {
    public int massage(int[] nums) {
        int n=nums.length;
        int[][] dp=new int[n+1][2];
        for(int i=1;i<=n;i++){
            dp[i][0]=Math.max(dp[i-1][1],dp[i-1][0]);
            dp[i][1]=dp[i-1][0]+nums[i-1];
        }
        return Math.max(dp[n][0],dp[n][1]);
    }
}

二、打家劫舍 II

213. 打家劫舍 II

题目描述:

题目分析:

其实这就是在上题的基础上做了一些限制。我们可以根据第一号房子是否选择来将问题抽象成两个模型:

  • 偷第一号房子:此能就不能去偷第二号房子与第n号房子,就是还能偷 3,n-1 区间内的房子
  • 不偷第一号房子:此时可以偷最后一个房子,也就是说可以偷 2,n 区间内的房子

我们可以将上题中的 "打家劫舍" 模型抽象出来,将求取 1,n 范围内的最优选改为设定的 l,r 范围内的最优选。只需要修改一下遍历时的边界即可

此时就将该问题抽象成了两个 "打家劫舍" 问题的最优解了。

代码实现:

java 复制代码
class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        return Math.max(nums[0]+fun(nums,3,n-1),fun(nums,2,n));
    }
    public static int fun(int[] nums,int l,int r){
        int[][] dp=new int[nums.length+1][2];
        for(int i=l;i<=r;i++){
            dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
            dp[i][1]=dp[i-1][0]+nums[i-1];
        }
        return Math.max(dp[r][0],dp[r][1]);
    }
    
}

三、删除并获得点数

740. 删除并获得点数

题目描述:

题目分析:

这题看上去似乎与 "打家劫舍" 毫不相关。但仔细观察我们会发现,当选择值为 numi 的数字时, numi -1与 numi +1就无法被选中了。这与不能连续选择元素的 "打家劫舍" 问题不谋而合

我们可以创建一个hash数组,将 numi 的值映射到 hash数组对应的下标。然后再对hash数组进行一次 "打家劫舍" 即可

代码实现:

java 复制代码
class Solution {
    public int deleteAndEarn(int[] nums) {
        int[] arr=new int[10001];
        for(int num:nums){
            arr[num]+=num;
        }
        int[] f=new int[10001],g=new int[10001];
        f[0]=arr[0];
        for(int i=1;i<10001;i++){
            f[i]=g[i-1]+arr[i];
            g[i]=Math.max(f[i-1],g[i-1]);
        }
        return Math.max(f[10000],g[10000]);
    }
}

四、粉刷房子

LCR 091. 粉刷房子

题目描述:

题目分析:

这个问题其实就是拓展版的 "打家劫舍" 问题。普通的 "打家劫舍" 问题每个状态只有 选与不选 两种,而该题有三种。但其实万变不离其中,仔细分析每种状态也能很好地做出来。

1、状态表示

  • 用 ri 表示第 i 个位置选择红色时的最小花费
  • 用 bi 表示第 i 个位置选择篮色时的最小花费
  • 用 gi 表示第 i 个位置选择绿色时的最小花费

2、状态转移方程

由于每个位置都有三种选择,也就对应了三种状态:

对于 ri

  • 如果第 i 个位置刷上红色,那么第 i-1 个位置上就只能刷 蓝色或绿色。因此我们只需要知道第 i-1 个位置刷上 蓝色或绿色 这两种情况中的最小花费,再加上 i 位置的花费即可。可得状态转移方程就为:

ri=Math.min(bi-1,gi-1)+costsi0

同理可以得出另外两种状态转移方程为:

bi=Math.min(ri-1,gi-1)+costsi1

gi=Math.min(ri-1,bi-1)+costsi2

4、初始化

为了避免 i-1 发生越界访问,我们可以先初始化当 i=0 时每种情况的初始值。而由于是首次选择,因此每组的最小花费就应该为对应的costs本身:

r0=costs00

b0=costs01

g0=costs02

代码实现:

java 复制代码
class Solution {
    public int minCost(int[][] costs) {
        int n=costs.length;
        int[] r=new int[n],b=new int[n],g=new int[n];
        r[0]=costs[0][0];
        b[0]=costs[0][1];
        g[0]=costs[0][2];
        for(int i=1;i<n;i++){
            r[i]=Math.min(b[i-1],g[i-1])+costs[i][0];
            b[i]=Math.min(r[i-1],g[i-1])+costs[i][1];
            g[i]=Math.min(r[i-1],b[i-1])+costs[i][2];
        }
        return Math.min(r[n-1],Math.min(b[n-1],g[n-1]));
    }
}

那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊

相关推荐
卷无止境6 分钟前
C++ 存储类说明符(Storage Class Specifier)大横评
c++·后端
卷无止境16 分钟前
C++ 编程的一大坑:非常量全局变量是"万恶之源"
c++·后端
C语言小火车17 分钟前
C++ 快速排序(Quick Sort)深度精讲:分治思想、Lomuto 分区法及三数取中优化,面试手撕必会
c语言·开发语言·c++·面试·排序算法·快速排序
地平线开发者21 分钟前
【地平线 征程 6 工具链进阶教程】QAT 训练常见问题和排查
算法
地平线开发者22 分钟前
征程 6 | 直方图量化配置与校准实例
算法
lzqrzpt42 分钟前
LED驱动电源选型标准与工程应用技术要点解析
python·单片机·嵌入式硬件·物联网
Mr-Wanter43 分钟前
wsl2 jdk管理工具之sdkman
java·开发语言·sdkman
Maiko Star1 小时前
Python核心语法——函数
开发语言·python
地平线开发者1 小时前
征程 6E/M Matrix 开发评板使用系列(一):开箱与点亮
算法·自动驾驶