day48 ● 198.打家劫舍 ● 213.打家劫舍II ● 337.打家劫舍III

127 阅读3分钟

将介绍如何使用动态规划解决三道打家劫舍问题:198.打家劫舍,213.打家劫舍II和337.打家劫舍III。

一、198.打家劫舍

题目描述:给定一个非负整数数组,代表每个房屋里的钱数,选择不相邻的房屋抢劫,求最大金额。

解题思路:使用动态规划,定义一个数组dp,dp[i]表示前i个房屋最大金额。则状态转移方程为:dp[i] = max(dp[i-1], dp[i-2]+nums[i]),即前i个房屋最大金额为前i-1个房屋最大金额或前i-2个房屋最大金额加上第i个房屋的钱数中的较大值。

Java代码如下:

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] = nums[0];
    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

题目描述:给定一个非负整数数组,代表每个房屋里的钱数,首尾相连的房屋不能同时抢劫,求最大金额。

解题思路:由于首尾相连的房屋不能同时抢劫,因此第一个房屋和最后一个房屋不能同时被抢。可以将此问题转换为两个198.打家劫舍问题,一个是从第一个房屋开始抢劫,不抢最后一个房屋;另一个是从第二个房屋开始抢劫,可以抢最后一个房屋。最终答案为这两个问题的最大值。

Java代码如下:

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

private int rob(int[] nums, int start, int end) {
    int pre2 = 0, pre1 = 0;
    for (int i = start; i <= end; i++) {
        int cur = Math.max(pre1, pre2+nums[i]);
        pre2 = pre1;
        pre1 = cur;
    }
    return pre1;
}

三、337.打家劫舍III

题目描述:给定一个二叉树,每个节点代表一个房屋,节点内的值代表该房屋内的钱数。不能抢劫相邻的节点,求最大金额。

解题思路:在二叉树上使用动态规划,定义一个数组dp,dp[i][0]表示以第i个节点为根节点且不抢劫该节点所获得的最大金额,dp[i][1]表示以第i个节点为根节点且抢劫该节点所获得的最大金额。对于每一个节点i,其最大金额有两种情况:抢劫该节点,则不能抢劫其左右子节点,因此dp[i][1] = i.val + dp[i.left][0] + dp[i.right][0];不抢劫该节点,则可以抢劫其左右子节点,因此dp[i][0] = max(dp[i.left][0], dp[i.left][1]) + max(dp[i.right][0], dp[i.right][1])。最终答案为根节点的两种情况的最大值。

Java代码如下:

public int rob(TreeNode root) {
    int[] res = robSub(root);
    return Math.max(res[0], res[1]);
}

private int[] robSub(TreeNode root) {
    if (root == null) {
        return new int[2];
    }
    int[] left = robSub(root.left);
    int[] right = robSub(root.right);
    int[] res = new int[2];
    // 抢劫该节点
    res[1] = root.val + left[0] + right[0];
    // 不抢劫该节点
    res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
    return res;
}

总结:以上三个问题都可以使用动态规划的思想解决。198.打家劫舍和213.打家劫舍II的状态转移方程比较简单,只需要考虑前一个状态和前两个状态的最大值即可。337.打家劫舍III在二叉树上使用动态规划,需要分别计算抢和不抢两种情况,最终答案取两种情况的最大值。