一、198.打家劫舍
需要注意的点:
- dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
- 决定dp[i]的因素就是第i房间偷还是不偷。
如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2] 加上第i房间偷到的钱。
如果不偷第i房间,那么dp[i] = dp[i - 1],即考虑i-1房,(注意这里是考虑,并不是一定要偷i-1房,这是很多同学容易混淆的点) - 从递推公式dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);可以看出,递推公式的基础就是dp[0] 和 dp[1]
从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1]);
然后dp[i]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
以下是代码部分:
public class 打家劫舍198 {
public int rob(int[] nums) {
//踩坑,没有看数组的长度范围
if(nums.length == 1)
return nums[0];
//dp数组,表示i之前(包括i)的最大金额
int[] dp = new int[nums.length];
//初始化
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
//遍历
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);
}
return dp[nums.length-1];
}
}
二、213.打家劫舍Ⅱ
需要注意的点:
由于第一个和最后一个只能考虑其中一个,所以将总的情况分成两个大情况( 将环形问题转换为直线型问题 ):
- 不考虑最后一个
- 不考虑第一个
没有第三种情况,所以两种情况的最大值就是最终解。
以下是代码部分:
public class 打家劫舍Ⅱ213 {
public int rob(int[] nums) {
if(nums.length == 1)
return nums[0];
int zero = solve(nums, 0, nums.length-1);
int one = solve(nums, 1, nums.length);
return Math.max(zero, one);
}
private int solve(int[] nums, int start, int end){
int[] dp = new int[end - start];
dp[0] = nums[start];
//踩坑:这里dp也有可能长度为1,所以也要记得判断
if(dp.length == 1)
return dp[0];
dp[1] = Math.max(nums[start], nums[start+1]);
for (int i = 2; i < dp.length; i++) {
//注意:nums[]中是 i+start (如果 i 等于1,相当于整体向右挪了一位)
dp[i] = Math.max(dp[i-2] + nums[i + start], dp[i-1]);
}
return dp[dp.length-1];
}
}
三、337.打家劫舍Ⅲ
其实与第一道题类似,不同点在于是遍历节点的方式。
需要注意的点:
- 必须使用后序遍历
- 需要记录孙子节点的信息。有两种方式:①使用map哈希表记录当前节点对应的最大金额,防止需要不停地调用;②递归函数的返回值可以存放两个数,一个数是不包含当前节点的最大金额(即孙子节点的信息记录在这里),另一个数是包含当前节点的最大金额。这里并没有取一个最大值,取最大值的操作交给了父亲节点。
以下是代码部分:
class Solution {
// 1.递归去偷,超时
public int rob(TreeNode root) {
if (root == null)
return 0;
int money = root.val;
if (root.left != null) {
money += rob(root.left.left) + rob(root.left.right);
}
if (root.right != null) {
money += rob(root.right.left) + rob(root.right.right);
}
return Math.max(money, rob(root.left) + rob(root.right));
}
// 2.递归去偷,记录状态
// 执行用时:3 ms , 在所有 Java 提交中击败了 56.24% 的用户
public int rob1(TreeNode root) {
Map<TreeNode, Integer> memo = new HashMap<>();
return robAction(root, memo);
}
int robAction(TreeNode root, Map<TreeNode, Integer> memo) {
if (root == null)
return 0;
if (memo.containsKey(root))
return memo.get(root);
int money = root.val;
if (root.left != null) {
money += robAction(root.left.left, memo) + robAction(root.left.right, memo);
}
if (root.right != null) {
money += robAction(root.right.left, memo) + robAction(root.right.right, memo);
}
int res = Math.max(money, robAction(root.left, memo) + robAction(root.right, memo));
memo.put(root, res);
return res;
}
// 3.状态标记递归
// 执行用时:0 ms , 在所有 Java 提交中击败了 100% 的用户
// 不偷:Max(左孩子不偷,左孩子偷) + Max(又孩子不偷,右孩子偷)
// root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) +
// Math.max(rob(root.right)[0], rob(root.right)[1])
// 偷:左孩子不偷+ 右孩子不偷 + 当前节点偷
// root[1] = rob(root.left)[0] + rob(root.right)[0] + root.val;
public int rob3(TreeNode root) {
int[] res = robAction1(root);
return Math.max(res[0], res[1]);
}
int[] robAction1(TreeNode root) {
int res[] = new int[2];
if (root == null)
return res;
int[] left = robAction1(root.left);
int[] right = robAction1(root.right);
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[1] = root.val + left[0] + right[0];
return res;
}
}