多状态动态规划之打家劫舍

简介: 多状态动态规划之打家劫舍

1. 题目分析



题目链接选自力扣 : 打家劫舍

image.png

根据示例 1 来看 :

5db5ea56dd0a93ade180493ebdc9fd8b.png

总结一下规则, 也就是可以从任意一个房间开始偷窃, 但是相邻房间不能偷窃. 并且只能往后面偷窃不能从后往前


2. 转态表示



以 i 位置为结尾, 表示从某一个位置开始偷窃到该位置时盗窃到的最大金额即 dp[i]

和之前" 按摩师 " 一题一样, 发现当偷到 i 位置的时候又有两种情况. 可以细分为两个更小的状态表示划分问题


  1. 盗窃 nums[i] 位置

这种情况下, 我们用 f[i] 表示, 即从某一个位置开始偷窃到 i 位置时, 同时偷窃 nums[i], 最终盗窃的最大金额


  1. 不盗窃 nums[i] 位置

这种情况我们用 g[i] 表示, 即从某一个位置开始偷窃到 i 位置时, 不偷窃 nums[i], 最终盗窃的最大金额


不难发现, 这又是一个多状态的动态规划题. 特点就是有多个状态表示, 同时也有多个状态转移方程.


3. 转态转移方程



根据上面的转态表示划分的两种情况, 此时我们的状态方程也会有两个.


  1. 偷窃到 i 位置时, 偷窃 nums[i]

f3f5e80bd140c2449bc45688164da2a9.png

当偷窃到 i 位置时, 选择偷窃 nums[i], 那么 i - 1 位置就是必定不能偷的, 否则会被抓. 因此偷窃到的最大金额就为从某一起始位置偷到 i - 1 之间的最大金额, 并且不偷窃 nums[i-1], 这正好对应到我们的转态表示中, 即 g[i-1], 最后加上选择偷窃的 nums[i]. 最终此时偷窃到 i 位置的最大金额为 f[i] = g[i-1] + nums[i]


  1. 偷窃到 i 位置时, 不偷窃 nums[i]

b85c942241d8dcafd8fe295db3f310a7.png


当偷窃到 i 位置时, 选择不偷窃 nums[i]. 那么这时候 i - 1 位置就有两种情况

  1. i - 1 位置偷 nums[i-1]

4e017303a84400e59175be4bf57fe027.png


这种情况下, 偷窃的最大金额为从某一起始位置到达 i - 1 位置并且偷窃 i - 1 位置的 nums[i-1]. 正好对应我们的转态表示f[i-1].

  1. i-1 位置不偷

484d5896986ed9acac4622ad5b124ecf.png

这种情况下, 偷窃到的最大金额为从某一位置起到达 i - 1 位置并且不偷窃 i - 1 位置的 nums[i-1]. 正好对应我们的转态表示 g[i-1]


最终不偷窃 i 位置的 nums[i] 的两种情况就分完了, 而我们要的是最大的金额数. 因此最终的状态转移方程为 g[i] = Math.max( f[i - 1], g [i - 1] )


4. 初始化



根据状态转移方程 g[i] = Math.max( f[i - 1], g [i - 1] 和 f[i] = g[i-1] + nums[i] 在填写 g[0] 和 f[0] 位置的时候会存在越界情况.


经过分析, 如果只有一个元素的情况下, 这时候偷窃到这个房间并且选择偷窃 nums[0], 最终的最大偷窃金额即位 nums[0]. 对应到状态表示中为 f[i] = nums[0]


同样一个元素的情况下, 偷窃到这个房间时, 可以选择不偷. 最终的最大偷窃金额为 0. 对应到状态表示中为 g[0] = 0


5. 填表顺序



从状态转移方程来看, 想要知道当前位置的最大偷窃金额, 就需知道前一个位置的最大偷窃金额. 因此填表顺序为 从左往右


6. 返回值



按照题目要求返回偷窃到数组末尾 ( n -1 ) 时最大的偷窃金额. 因此要对这两种状态取最大值. 即为 Math.max( g[n-1], f[n-1] ) ( 注意下标之间的关系 )


7. 代码演示



class Solution {
    public int rob(int[] nums) {
        // 1. 创建 dp 表
        int n = nums.length;
        int[] f = new int[n]; // 偷窃到 i 位置时,偷窃 nums[i]
        int[] g = new int[n]; // 偷窃到 i 位置时, 不偷窃nums[i]
        // 2. 初始化
        f[0] = nums[0];
        g[0] = 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(g[n - 1], f[n - 1]);
    }
}


相关文章
|
前端开发 测试技术 持续交付
成功的上线之路:前端部署策略详解
前端部署是将您的Web应用从开发环境转移到生产环境的关键步骤。它不仅影响网站的可用性和性能,还涉及到安全性和用户体验。在本博客中,我们将深入研究前端部署的概念、最佳实践以及如何选择适合您项目的部署策略。
907 0
|
2月前
|
vr&ar 开发工具 C#
基于Rokid使用Unity开发3D轮盘抽奖游戏:虚实交互实战全解析
本文详解如何基于Rokid AR Lite与UXR3.0 SDK,在Unity中开发轻量、沉浸式3D轮盘抽奖AR游戏:涵盖环境搭建、3D场景构建、多模态交互(射线/触控)、旋转物理逻辑、中奖判定及性能优化,助力开发者快速落地虚实融合趣味应用。(239字)
|
算法 Python
Python中基本的循环结构
Python中基本的循环结构
354 1
|
机器学习/深度学习 网络架构 开发者
YOLOv8改进 | 2023 | DiverseBranchBlock多元分支模块(有效涨点)
YOLOv8改进 | 2023 | DiverseBranchBlock多元分支模块(有效涨点)
489 0
|
人工智能 自然语言处理 算法
|
存储 监控 供应链
微服务拆分的 “坑”:实战复盘与避坑指南
本文回顾了从2~3人初创团队到百人技术团队的成长历程,重点讨论了从传统JSP到前后端分离+SpringCloud微服务架构的演变。通过实际案例,总结了微服务拆分过程中常见的两个问题:服务拆分边界不清晰和拆分粒度过细,并提出了优化方案,将11个微服务优化为6个,提高了系统的可维护性和扩展性。
371 0
|
搜索推荐 数据管理
5分钟快速定制企业年度报告
钉钉数据资产平台致力于打造数据消费的新方式,助力企业在数据驱动的时代中脱颖而出。
|
缓存 JavaScript 前端开发
探索Vue.js中的计算属性与侦听器
【10月更文挑战第5天】探索Vue.js中的计算属性与侦听器
286 1
|
编译器 C++ 开发者
什么是内联函数?
综上所述,内联函数在C++中是一种重要的优化手段,特别是在类定义中,通过内联简单的成员函数,可以提高程序的执行效率、代码的可读性和维护性,同时为编译器提供更多的优化机会。然而,需要注意的是,并不是所有函数都适合内联,具体情况需要根据函数的复杂度和实际需求进行权衡。
250 0