多状态动态规划之按摩师

简介: 多状态动态规划之按摩师

1. 题目分析



题目链接选自力扣 : 面试题 17.16. 按摩师

d9fd2a85f0b32089ecaaeccef458408f.png


还是一样, 根据给的示例 1 来分析看看 :

8407b1b43bd5cae6502b650a5492c138.png


进过分析, 一共有这样几个规定 :

  1. 相邻预约不能同时接受
  2. 可以从任意一个预约开始
  3. 不能之前的
  4. 最终总的预约时长要最长的


2. 状态表示



以 i 位置为结尾, 表示从某一个位置预约开始到 i 位置结束时的预约最长时间. 用 dp[i] 表示.

对于这个题来说, 它还有一点特殊. 你会发现, 当你以上面的状态表示时, 还可以分为其它状态. 什么意思呢 ?

598685f637ed380004673a3b0552720c.png

  1. 当达到 i 位置后, 预约了 i 位置

我们把这种情况以 f[i] 表示, 即以某一个位置预约开始到 i 位置结束时, nums[i] 必选, 此时的最长预约时间


  1. 当到达 i 位置后, 不预约 i 位置

我们把这种情况以 g[i] 表示, 即以某一个位置月月开始到 i 位置结束时, nums[i] 不选, 此时的最长预约时间


这种多种状态细分状态表示的问题我们称为多状态动态规划. 特点就是状态多可以接着细分 ! 有多个不同状态下的状态转移方程 ! ! !


3. 状态转移方程



同样以最近的一步来划分问题. 由于我们把这个问题更加细分了, 状态转移方程也就和之前的有一点不一样了. 一起来看看

  1. 到达 i 位置时, 预约 nums[i]

81c54d24d485c1ed6e56a78ad110ef6f.png


这种情况下, 选择了 nums[i], 相邻的 nums[i-1] 就无法预约了. 只需要知道从起始位置到达 i - 1 位置时的最长预约时间加上 nums[i] 就是最终预约时间. 而这对应到我们的状态方程中则为 g[i-1].


最终为这种情况下的最长预约时长为: g[i-1] + nums[i]

  1. 到达 i 位置时, 不预约 nums[i]


当到达 i 位置时, 不预约 nums[i], 那么意味着前面一个 nums[i-1] 有两种情况

  1. 预约 nums[i-1]

af01e3044ebcf854ef3da93da5f1324c.png

根据状态转移方程, 这时候的最长预约时长就是从起始位置到 i-1 并预约 nums[i-1] 的最长预约时长. 正好对应我们的状态表示, 即为 f[i-1]

  1. 拒绝 nums[i-1]

a89569868c0a2fc51a4b16642ce69b7f.png根据状态转移方程, 这时候的最长预约时长就是从起始位置到达 i - 1 位置是的最长预约 时长. 正好对应我们的状态表示, 即为 g[i-1]


最终到达 i 位置时不选择 nums[i] 细分的两种情况选择预约时长最长的那种, 最终结果为 : Math.max( f[i - 1], g[i - 1] )

这里需要注意的是, 由于我们是多状态的转台表示. 因此最终的结果是需要分开进行处理的. 也就是最终的状态转移方程有两个

f[i] = g[i-1] + nums[i]

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


4. 初始化



在填写 f[i] 时根据状态转移方程 f[i] = g[i-1] + nums[i] 和 **Math.max( f[i - 1], g[i - 1] ) **会存在越界情况. 因此我们需要初始化 g[0] 和 f[0].


经过分析, 当只有一个元素时. 最长的预约时长就是 nums[0] 它是必选的, 对应到我们的状态表示则为 f[0] = nums[0].


同样, 当只有一个元素时, 可以选择拒绝不预约, 此时的最长预约时间就是 0. 对应到我们的状态表示则为 g[0] = 0


5. 填表顺序



填表顺序还是很清晰的, 一个线性的表, 从左往右填写即可.


6. 返回值



根据题目要求, 返回到达指定数组的末尾位置时最长的预约时间. 而我们的转态表示有两种情况. 一种到达结尾时预约该点. 一种不预约最终都有一个最长预约时间. 而我们要的是两种情况的最长预约时间. 因此返回值为 Math.max( f[n-1], g[i-1] ) ( 注意下标的对应关系 )


7. 代码演示



class Solution {
    public int massage(int[] nums) {
        // 1. 创建 dp 表
        // 有两个状态表示, 需要两个 dp 表
        int n = nums.length;
        int[] f = new int[n]; // 表示预约 nums[i]
        int[] g = new int[n]; // 表示不预约 nums[i]
        // 特殊情况处理, 防止 f[0] 初始化越界
        if(n == 0) {
            return 0;
        }
        // 2. 初始化
        f[0] = nums[0];
        g[0] = 0;
        // 3. 填写 dp 表
        for(int i = 1; i < n; i++) {
            // 根据状态转移方程填写
            f[i] = g[i - 1] + nums[i];
            g[i] = Math.max(f[i - 1], g[i -1]); 
        }
        // 4. 确认返回值
        return Math.max(f[n - 1], g[n - 1]);
    }
}


相关文章
|
1月前
动态规划——状态 dp
本文介绍了多个动态规划问题的解法,包括按摩师问题(即打家劫舍),通过 `dp` 数组追踪选与不选的最大收益;打家劫舍 II 将环形问题转化为线性;删除并获得点数问题,将数组处理后按打家劫舍规则求解;粉刷房子问题,使用三维 `dp` 数组追踪每种颜色的最小成本,每个问题都详细说明了状态表示、转移方程及初始化等关键步骤,并附有代码实现。
24 2
|
5月前
|
算法
【动态规划】速解简单多状态类问题
【动态规划】速解简单多状态类问题
|
5月前
|
机器学习/深度学习 存储 算法
LeetCode题目 90:五种算法 回溯\迭代\位掩码\字典树\动态规划实现 子集ll
LeetCode题目 90:五种算法 回溯\迭代\位掩码\字典树\动态规划实现 子集ll
|
6月前
|
算法 测试技术 C++
【动态规划 状态机dp 性能优化】3098. 求出所有子序列的能量和
【动态规划 状态机dp 性能优化】3098. 求出所有子序列的能量和
【动态规划 状态机dp 性能优化】3098. 求出所有子序列的能量和
|
6月前
|
Shell 网络安全
【错题集-编程题】mari和shiny(动态规划-多源状态线性 dp)
【错题集-编程题】mari和shiny(动态规划-多源状态线性 dp)
|
6月前
|
算法 测试技术 C#
【状态机dp 动态规划】100290. 使矩阵满足条件的最少操作次数
【状态机dp 动态规划】100290. 使矩阵满足条件的最少操作次数
|
6月前
|
算法 测试技术 C++
【动态规划 状态机dp】3082. 求出所有子序列的能量和
【动态规划 状态机dp】3082. 求出所有子序列的能量和
【图论】【深度优先搜索】【换根法】2858. 可以到达每一个节点的最少边反转次数
【图论】【深度优先搜索】【换根法】2858. 可以到达每一个节点的最少边反转次数
|
6月前
|
测试技术
【动态规划】【记忆化搜索】【状态压缩】1681. 最小不兼容性
【动态规划】【记忆化搜索】【状态压缩】1681. 最小不兼容性
|
6月前
|
算法 测试技术 C#
【贪心算法】【中位贪心】LeetCode:100123.执行操作使频率分数最大
【贪心算法】【中位贪心】LeetCode:100123.执行操作使频率分数最大