LeetCode ---- 打家劫舍系列问题思路与题解
Posted TheWhc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode ---- 打家劫舍系列问题思路与题解相关的知识,希望对你有一定的参考价值。
打家劫舍
198. 打家劫舍
/**
* 思路: 动态规划
* 1. 确定dp数组以及下标含义
* dp[i]: 考虑下标i(包括i)以内的房屋,最多可以偷窃的金额dp[i]
*
* 2. 确定递推公式
* dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
* 不偷第i号房屋 偷第i号房屋
* 3. 初始化
* dp[0] = nums[0]
* dp[1] = Math.max(nums[0], nums[1])
*
* 4. 确定遍历顺序
* 从前往后遍历
*
* 5. 举例推导dp数组
* 2 7 9 3 1
* dp数组 2 7 11 11 12
* 时间: O(n)
* 空间: O(n)
*/
public int rob(int[] nums)
if(nums == null || nums.length == 0)
return 0;
if(nums.length == 1)
return nums[0];
int[] dp = new int[nums.length];
// dp[0]一定是1号房屋的值
dp[0] = nums[0];
// 取决于1号和2号房屋的最大值
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < nums.length; i++)
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
return dp[nums.length-1];
213. 打家劫舍II
/**
* 思路: 动态规划
*
* 成环的偷盗问题分为三种情况:
* 1. 不考虑首尾元素偷盗
* 2. 考虑首部元素, 不考虑尾部元素偷盗
* 3. 考虑尾部元素, 不考虑首部元素偷盗
*
* 其中2和3情况已经包括了1, 所以1情况可以忽略
* 所以最后结果取 Math.max(情况2,情况3)
*
* 时间: O(n)
* 空间: O(n)
*/
public int rob(int[] nums)
if(nums == null || nums.length == 0)
return 0;
if(nums.length == 1)
return nums[0];
return Math.max(robRange(nums, 0, nums.length-1), robRange(nums, 1, nums.length));
private int robRange(int[] nums, int start, int end)
int[] dp = new int[end - start];
if(dp.length == 1)
return nums[start];
dp[0] = nums[start];
dp[1] = Math.max(nums[start], nums[start+1]);
for (int i = 2; i < dp.length; i++)
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[start + i]);
return dp[dp.length-1];
337. 打家劫舍III
树形dp
// 题意: 如果偷取了当前节点,那么它的孩子节点都不能偷取; 如果不偷取当前节点,那么它的孩子节点都能偷取
/**
* 思路: 树形dp
*
* 树形dp: 在树上进行递归公式的推导
*
* 对一个节点偷与不偷得到的最大金额进行记录,然后才能用状态转移记录状态的变化
*
* 递归函数
* 1. 确定递归函数的参数和返回值
* - 返回值为一个长度为2的数组,表示偷与不偷两个状态
* - int[] robTree(TreeNode cur)
*
* 2. 确定终止条件
* (空节点时,偷与不偷都是为0)
* - if(cur == null)
* return new int[]0, 0;
*
*
* 3. 确定遍历顺序
* 后序遍历(左右根)
* 通过递归得到左节点,得到左节点偷与不偷的金额
* 通过递归得到右节点,得到右节点偷与不偷的金额
* 下标为0表示不偷, 下标为1表示偷
* int[] left = robTree(cur.left); // 左
* int[] right = robTree(cur.right); // 右
*
* 4. 确定单层递归的逻辑
* 如果偷取当前节点,那么孩子节点都不能偷取, int val1 = cur.val + left[0] + right[0]
* 如果不偷取当前节点,那么孩子节点都能偷取,偷不偷选一个最大的值, int val2 = Math.max(left[0],left[1]) + Math.max(right[0],right[1])
*
* 5. 举例推导
* 3 6,7
* / \\
* 3,22 3 1,3
* \\ \\
* 0,33 1 0,1
*
* 时间: O(n)
* 空间: O(logn)
*/
public int rob(TreeNode root)
if(root == null)
return 0;
int[] res = robTree(root);
return Math.max(res[0], res[1]);
private int[] robTree(TreeNode cur)
// 递归终止条件
if(cur == null)
return new int[]0, 0;
// 后序遍历
int[] left = robTree(cur.left); // 左
int[] right = robTree(cur.right); // 右
// 单层递归逻辑
// 偷当前节点 + 左节点不偷 + 右节点不偷
int val1 = cur.val + left[0] + right[0];
// 不偷取当前节点 + 偷左节点中最大值 + 偷右节点最大值
int val2 = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
return new int[]val2, val1;
递归 + 记忆化
/**
* 思路: 递归 + 记忆化
*
* 实时计算每个节点偷与不偷的情况
*
* 时间: O(n)
* 空间: O(logn)
*/
// 记忆化避免重复计算
Map<TreeNode, Integer> map = new HashMap<>();
public int rob(TreeNode root)
if(root == null)
return 0;
if(root.left == null && root.right == null)
return root.val;
if(map.containsKey(root))
return map.get(root);
// 偷取当前节点
int val1 = root.val;
if(root.left != null)
val1 += rob(root.left.left) + rob(root.left.right);
if(root.right != null)
val1 += rob(root.right.left) + rob(root.right.right);
// 不偷取当前节点
int val2 = rob(root.left) + rob(root.right);
// 记录当前结果
map.put(root, Math.max(val1, val2));
return Math.max(val1, val2);
总结
以上是关于LeetCode ---- 打家劫舍系列问题思路与题解的主要内容,如果未能解决你的问题,请参考以下文章