LeetCode刷题day48|198.打家劫舍、213.打家劫舍Ⅱ、337.打家劫舍Ⅲ

一、198.打家劫舍

需要注意的点:

  1. dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
  2. 决定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房,这是很多同学容易混淆的点)
  3. 从递推公式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.打家劫舍Ⅱ

需要注意的点:
由于第一个和最后一个只能考虑其中一个,所以将总的情况分成两个大情况( 将环形问题转换为直线型问题 ):

  1. 不考虑最后一个
  2. 不考虑第一个

没有第三种情况,所以两种情况的最大值就是最终解。

以下是代码部分:

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.打家劫舍Ⅲ

其实与第一道题类似,不同点在于是遍历节点的方式。
需要注意的点:

  1. 必须使用后序遍历
  2. 需要记录孙子节点的信息。有两种方式:①使用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;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值