LeetCode Day48 198&213&337

45 阅读2分钟
198. 打家劫舍

01背包问题抽象:数组里各家的金额就是可以装进背包的金额,不能连着2个数取的情况下,求背包能装入的最大值是多少。

  1. dp[i]数组及其含义:在能盗取i元的情况下dp[i]是当前劫到的最多的金额
  2. 递推公式:因为每户人家只能打劫一次,并且依赖第i-2户人家打劫所得的金额。这里我们假设偷第i户人家,这种情况就是依赖i-2的,假设我们并不偷第i户人家,那说明我们在第i-1户人家的时候已经偷过了,所以有Math.max(dp[i-2] + num[i], dp[i-1])。
  3. 数组初始化:从递推公式来看dp[0]一定是nums[0],至于dp[1]就是num[0]和num[1]之间的最大值。
  4. 遍历顺序:dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历。
class Solution {
    public int rob(int[] nums) {
        if(nums.length == 0 || nums == null) return 0;
        if(nums.length == 1) return nums[0];
        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. 打家劫舍 II

这题相比第一题难点就在成环,一看评论区发现其实只需要分开讨论就好了…… 也就是说用同样的方法,先处理0到n-1户人家能取到的最大值,再处理1到第n户人家能取到的最大值,最后两者求max就是结果了。

class Solution {
    public int rob(int[] nums) {
        if(nums.length == 0 || nums == null) return 0;
        if(nums.length == 1) return nums[0];
        int n = nums.length - 1;
        return Math.max(robAction(nums, 0, n - 1), robAction(nums, 1, n));
    }

    public int robAction(int[] nums, int start, int end){
        if (end == start) return nums[start];
        int[] dp = new int[nums.length];
        dp[start] = nums[start];
        dp[start + 1] = Math.max(nums[start+1], nums[start]);
        for(int i = start + 2; i <= end; i ++){
            dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);
        }

        return dp[end];
    }
}
337. 打家劫舍 III

因为是在树上进行状态转移所以这题是树形dp。

  1. 确定递归函数的参数和返回值:这里每个树形结点有偷 与 不偷 两种状态,所以我们可以把下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
  2. 终止条件:在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回。
  3. 确定dp顺序:明确的是使用后序遍历。 因为要通过递归函数的返回值来做下一步计算。通过递归左节点,得到左节点偷与不偷的金钱。通过递归右节点,得到右节点偷与不偷的金钱。
  4. 单层递归的逻辑:如果是偷当前节点,那么左右孩子就不能偷;如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的。
class Solution {
    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 rob(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;
    }
}