【动态规划】打家劫舍
打家劫舍
力扣上打家劫舍相关的题目如下:
序号 | 题目 | 区别 |
---|---|---|
1 | 198. 打家劫舍 | 不能偷窃相邻的房间 |
2 | 213. 打家劫舍 II | 房间连成环 |
3 | 337. 打家劫舍 III | 房间组成一棵二叉树 |
应用
应用1:Leetcode.198
题目
分析
设
边界条件
当只有一个房间时,显然最大收益,就是选择偷窃该房间所所得的收益,当只有两个房间时,收益就是两个房间的最大值,因此边界条件为:
状态转移
当
-
不选择偷窃这个房间,那么,可以获取的最大金额就是
; -
选择偷窃这个房间,那么,可以获取的最大金额就是
;
综上,能获取的最大金额就是上述两种情况的最大值,即状态转移方程:
代码实现
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
题目
分析
设
题目中的限制条件,可以等价于:
-
若偷窃了第一个房间,就不能偷窃最后一个房间,即可以偷窃房间的范围为:
; -
若没有偷窃第一个房间,就可以偷窃最后一个房间,即可以偷窃房间的范围为:
。
因此,我们只需要针对上述两种情况,利用前面一道题的分析结果,分别计算两种情况的最大值,最后,再选择最优解即可。
代码实现
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
题目
分析
由于,我们一般采用递归的方式遍历一棵树,所以,求解树上的动态规划时,通常采用自顶向下的方式,通过分解子问题的方式求解。
对于这道题,我们注意到对于任意一个节点,它都会有两种状态:选择当前节点或者不选择节点。
因此,在遍历二叉树的过程中,为了表示两种状态,我们定一个递归函数:int [] doRob(TreeNode root)
,它会返回一个数组
对于任意一个节点
-
选择当前节点,那么,此时的收益就是:当前节点的值,加上不选择两个子节点对应的收益;
-
不选当前节点,那么,此时的收益就是:两个子节点的收益之和。
其中,每一个子节点的最大收益等于,选择该子节点的收益或者不选择该子节点的收益的最大值;
这里,我们选择通过后序遍历的方式计算每个节点收益,即在离开当前节点的时候,计算当前节点的收益。
代码实现
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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步