LeetCode——打家劫舍问题

Q:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
  偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
  偷窃到的最高金额 = 2 + 9 + 1 = 12 。

A:典型的动态规划问题。
当前房子分成两种可能,要么抢了,要么没抢。
如果当前抢了,要么是上一次没抢,要么是上上次抢了;
如果当前没抢,要么是上次抢了,要么是上上次抢了(为了能让下次抢,这次先不抢);
这样,dp就很好写了:

dp[i][0] = Math.max(dp[i - 1][1], dp[i - 2][1]);//要么是上次抢了,要么是上上次抢了
dp[i][1] = Math.max(dp[i - 1][0] + nums[i], dp[i - 2][1] + nums[i]);//要么是上一次没抢,要么是上上次抢了

代码:

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

或者,从尾走到头:(引用自《labuladong的算法小抄》)

// 返回 nums[start..] 能抢到的最⼤值
private int dp(int[] nums, int start) {
    if (start >= nums.length) {
    return 0;
    }
    int res = Math.max(
        // 不抢,去下家
        dp(nums, start + 1),
        // 抢,去下下家
        nums[start] + dp(nums, start + 2)
    );
    return res;
}

这也可以添加memo防止重复计算。
这是自顶向下的,也可以自底向上,减少计算

int rob(int[] nums) {
    int n = nums.length;
    // dp[i] = x 表⽰:
    // 从第 i 间房⼦开始抢劫,最多能抢到的钱为 x
    // base case: dp[n] = 0
    int[] dp = new int[n + 2];
    for (int i = n - 1; i >= 0; i--) {
        dp[i] = Math.max(dp[i + 1], nums[i] + dp[i + 2]);
    }
    return dp[0];
    }

因为只和dp[i + 1],dp[i + 2]相关,可以只用两个变量记录一下。

Q:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
  偷窃到的最高金额 = 1 + 3 = 4 。

A:
⾸先,⾸尾房间不能同时被抢,那么只可能有三种不同情况:要么都不被抢;要么第⼀间房⼦被抢最后⼀间不抢;要么最后⼀间房⼦被抢第⼀间不抢。

那就简单了啊,这三种情况,那种的结果最⼤,就是最终答案呗!不过,其实我们不需要⽐较三种情况,只要⽐较情况⼆和情况三就⾏了,因为这两种情况对于房⼦的选择余地⽐情况⼀⼤呀,房⼦⾥的钱数都是⾮负数,所以选择余地⼤,最优决策结果肯定不会⼩。带入上面的就可以了。

    public static int rob(int[] nums) {
        int n = nums.length;
        if (n == 0)
            return 0;
        if (n == 1)
            return nums[0];
        return Math.max(maxRob(nums, 0, n - 2), maxRob(nums, 1, n - 1));
    }

    private static int maxRob(int[] nums, int start, int end) {
        int dp_2 = 0;
        int dp_1 = 0;
        int dp = 0;
        for (int i = end; i >= start; i--) {
            dp = Math.max(dp_1, dp_2 + nums[i]);
            dp_2 = dp_1;
            dp_1 = dp;
        }
        return dp;
    }

Q:在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:
输入: [3,2,3,null,3,null,1]
3
/
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:
输入: [3,4,5,1,3,null,1]
  3
/
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

A:
看到树,就递归。

    Map<TreeNode, Integer> map = new HashMap<>();

    public int rob(TreeNode root) {
        if (root == null)
            return 0;
        if (map.containsKey(root))
            return map.get(root);
        int do_it = root.val + (root.left == null ? 0 : rob(root.left.left) + rob(root.left.right)) + (root.right == null ? 0 : rob(root.right.left) + rob(root.right.right));
        int not_do = rob(root.left) + rob(root.right);
        int res = Math.max(do_it, not_do);
        map.put(root, res);
        return res;
    }
posted @ 2020-03-29 12:41  Shaw_喆宇  阅读(242)  评论(0编辑  收藏  举报