2023-01-16 21:55阅读: 74评论: 0推荐: 0

【动态规划】打家劫舍

打家劫舍

力扣上打家劫舍相关的题目如下:

序号 题目 区别
1 198. 打家劫舍 不能偷窃相邻的房间
2 213. 打家劫舍 II 房间连成环
3 337. 打家劫舍 III 房间组成一棵二叉树

应用

应用1:Leetcode.198

题目

198. 打家劫舍

分析

dp[i] 表示前 i 个房间能获取的最大金额,设房间数量为 n,那么, 0in1

边界条件

当只有一个房间时,显然最大收益,就是选择偷窃该房间所所得的收益,当只有两个房间时,收益就是两个房间的最大值,因此边界条件为:

dp[0]=nums[0]dp[1]=max(nums[0], nums[1])

状态转移

i2 时,对于第 i 个房间nums[i],有两种选择:

  • 不选择偷窃这个房间,那么,可以获取的最大金额就是 dp[i1]

  • 选择偷窃这个房间,那么,可以获取的最大金额就是 dp[i2]+nums[i];

综上,能获取的最大金额就是上述两种情况的最大值,即状态转移方程:

dp[i]=max(dp[i1], dp[i2]+nums[i]),i2

代码实现

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

对其压缩状态,优化后的实现:

class Solution:
def rob(self, nums: List[int]) -> int:
n = len(nums)
if n == 1:
return nums[0]
a = nums[0]
b = max(nums[1], nums[0])
c = b
for i in range(2, n):
c = max(b, a + nums[i])
a = b
b = c
return c

应用2:Leetcode.213

题目

213. 打家劫舍 II

分析

dp[i] 表示前 i 个房间能获取的最大金额,设房间数量为 n

题目中的限制条件,可以等价于:

  • 若偷窃了第一个房间,就不能偷窃最后一个房间,即可以偷窃房间的范围为:nums[0], , nums[n2]

  • 若没有偷窃第一个房间,就可以偷窃最后一个房间,即可以偷窃房间的范围为:nums[1], , nums[n1]

因此,我们只需要针对上述两种情况,利用前面一道题的分析结果,分别计算两种情况的最大值,最后,再选择最优解即可。

代码实现

class Solution {
public int rob(int[] nums) {
int n = nums.length;
if (n == 1) {
return nums[0];
}
int[] nums1 = Arrays.copyOfRange(nums, 0, n - 1);
int[] nums2 = Arrays.copyOfRange(nums, 1, n);
return Math.max(doRob(nums1), doRob(nums2));
}
private int doRob(int[] nums) {
int n = nums.length;
if (n == 1) {
return nums[0];
}
int [] dp = new int [n];
dp[0] = nums[0];
dp[1] = Math.max(nums[1], nums[0]);
boolean canUseLastRoom = n % 2 == 0;
for (int i = 2; i < n; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
}

应用3:Leetcode.337

题目

337. 打家劫舍 III

分析

由于,我们一般采用递归的方式遍历一棵树,所以,求解树上的动态规划时,通常采用自顶向下的方式,通过分解子问题的方式求解。

对于这道题,我们注意到对于任意一个节点,它都会有两种状态:选择当前节点或者不选择节点。

因此,在遍历二叉树的过程中,为了表示两种状态,我们定一个递归函数:int [] doRob(TreeNode root) ,它会返回一个数组 [profit1,profit2],表示当前节点 node 可以获取的收益,其中数组的值 profit[0]profit[1] 分别表示选择该节点的最大收益不选择该节点的最大收益

对于任意一个节点 node,我们都有两种选择:选择当前节点或者不选择节点,因此:

  • 选择当前节点,那么,此时的收益就是:当前节点的值,加上不选择两个子节点对应的收益;

  • 不选当前节点,那么,此时的收益就是:两个子节点的收益之和。

    其中,每一个子节点的最大收益等于,选择该子节点的收益或者不选择该子节点的收益的最大值;

这里,我们选择通过后序遍历的方式计算每个节点收益,即在离开当前节点的时候,计算当前节点的收益。

代码实现

class Solution {
public int rob(TreeNode root) {
int [] profits = doRob(root);
return Math.max(profits[0], profits[1]);
}
private int [] doRob(TreeNode root) {
if (root == null) {
return new int[2];
}
// 左子树的收益
int [] left = doRob(root.left);
// 右子树的收益
int [] right = doRob(root.right);
// 选择当前节点:此时,左右子树都能不能选择
int profit1 = root.val + left[1] + right[1];
// 不选择当前节点:此时,左右子树各自取最大值即可
int profit2 = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
return new int [] {profit1, profit2};
}
}

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/17055009.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(74)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.