198.打家劫舍
- 1、题目
- 2、题目分析
- 3、解题步骤
- 4、复杂度最优解代码示例
- 5、抽象与扩展
1、题目
一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响小偷偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组 nums
,请计算 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:nums = [1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:nums = [2,7,9,3,1] 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
- 数组
- 动态规划
2、题目分析
1、定义出状态:可使用问题即答案的方式,借助问题定义状态,比如问题是“一夜之内能够偷窃到的最高金额。”,即状态可以初步定义为每个房屋,及抵达该房屋时所能偷窃的最大值。那么最终状态,就是答案。
2、推导出状态转移方程:主要是基于动态规划的三个特性来分析题目
注意:动态规划的三个特性,都是要站在状态的角度,而不是站在入参数组的角度。
-
重复子问题:
要求1、存在子问题 (站在状态的角度考虑该特性)
思考:子问题就是到达各个房屋所能偷窃的最大值。
要求2、对每个子问题有相同的决策方案 (站在入参数组的角度考虑该特性)
思考:对每个子问题的决策方案都是2个:①决定偷窃当前房屋、②决定不偷窃该房屋。 -
最优子结构:(站在状态的角度考虑该特性)
问题的最优解可以由子问题的最优解推导出来。这意味着后面阶段的状态仅由前面某阶段的状态即可推导出来。
求本状态最优解:(对每个房屋都有2个选择,偷窃、或不偷窃)
1.本状态不偷窃,则本状态值=上一状态最优解
2.本状态要偷窃,则本状态值=上上状态最优解 + 本房屋的价值
本状态最优解 = Math.max(上一状态最优解, 上上状态最优解 + 本房屋的价值)
某状态最优解可由上状态最优解、上上状态最优解推导出来。故本问题满足最优子结构的要求 -
无后效性:(站在状态的角度考虑该特性)
要求1、在推导后面阶段的状态的时候,我们只关心前面某阶段的状态值,不关心这个状态是怎么一步一步推导出来的。
思考:由2.最优子结构的分析已知,某状态最优解只由上状态最优解、上上状态最优解推导出来。
要求2、某阶段状态一旦确定,就不受之后阶段的决策影响。无后效性是一个非常“宽松”的要求。只要满足前面提到的动态规划问题模型,其实基本上都会满足无后效性。
思考:到达第i个房子所能偷到的最大金额,跟后续阶段的决策无关,仅有各阶段的决策决定。故本问题满足这2个无后效性的要求
3、解题步骤
1、初始化状态:基于状态转移方程,考虑需要初始化哪些状态
本状态最优解 = Math.max(上一状态最优解, 上上状态最优解 + 本房屋的价值)
如从该方程可知,我们需要初始化 2 个状态,因为某状态就需要前面 2 个阶段的状态推导出来。
2、迭代状态转移方程
4、复杂度最优解代码示例
public int rob(int[] nums) {int[] dp = new int[nums.length + 1];// 类似于链表的哨兵,无实际含义,方便该dp代码的编写dp[0] = 0;// 初始化到达第1间房屋时所能偷窃到的最大值dp[1] = nums[0];for(int i = 2; i <= nums.length; i++) {// 1.本状态不偷窃,则本状态值=上一状态最优解// 2.本状态要偷窃,则本状态值=上上状态最优解 + 本房屋的价值// 本状态最优解 = Math.max(上一状态最优解, 上上状态最优解 + 本房屋的价值)dp[i] = Math.max(dp[i - 1], nums[i - 1] + dp[i - 2]);}return dp[nums.length];}
5、抽象与扩展
通用动态规划的解法,见标题二