动态规划--打家劫舍

Posted 戚焱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划--打家劫舍相关的知识,希望对你有一定的参考价值。

动态规划-----打家劫舍

偷还是不偷,这是个问题。

打家劫舍Ⅰ

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 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 。

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目大意为小偷偷窃,不能偷相邻的两家。小偷若要偷1号位置,就不能偷0号位置。同时,小偷若想偷2号位置,就不能偷1号位置,但是不相邻的0号位置是可以偷的。

在这里插入图片描述

上面图片,小偷的最大收益为0号位置+2号位置=9.即小偷在0号房间选择偷,1号不偷,二号偷。在0号房间时,最大收益为4,1号房间的最大收益为7。2号房间的最大收益为4+5=9.

在这里插入图片描述

上面图片,小偷的最大收益为0号位置+2号位置=9.即小偷在0号房间选择偷,1号不偷,二号偷。在0号房间时,最大收益为4,1号房间的最大收益仍为4,因为此时1号房间的收益没有0号房间高,小偷是肯定不会偷1号房间的。2号房间的最大收益为4+5=9.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6R3hCmRm-1621212255023)(动态规划-----打家劫舍.assets/image-20210514163014789.png)]

上面图片,小偷最大的收益为0号位置+3号位置=4。即小偷在0号房间选择偷,1号不偷,二号不偷,三号偷。

小偷在0号房间时,最大的收益就是2号房间的价值:2。在一号房间时,小偷思考的问题是偷0号房间与1号房间哪个收益更高,2>1,0号房间收益更高,小偷不会偷1号房间,所以小偷在1号房间的最大收益仍为2。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0rtOX00-1621212255024)(动态规划-----打家劫舍.assets/image-20210514170349217.png)]

小偷在2号房间时,考虑:2号房间收益+0号房间的最大收益与1号房间的最大收益哪个更高,2+1>2,显然2号房间的最大收益为3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-smJbc3Wl-1621212255024)(动态规划-----打家劫舍.assets/image-20210514170455333.png)]

小偷来到3号房间,考虑:3号房间的收益+1号房间的最大收益与2号房间的最大收益哪个更高,2+2>3,显然3号房间的最大收益为:4

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMpn9WJT-1621212255025)(动态规划-----打家劫舍.assets/image-20210514171529194.png)]

用dp[i]来表示第i个房间的最大收益,那么可以得到

dp[i]=Math.max(dp[i-1],dp[i-2]+目前位置的收益)

代码的编写就很简单了:

class Solution {
    public int rob(int[] nums) {
        if(nums.length<2){
            return nums[0];
        }
        if(nums.length<3){
            return Math.max(nums[0],nums[1]);
        }
        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];
    }
}

打家劫舍Ⅱ

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

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

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

输入:nums = [0]
输出:0

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 1000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

与打家劫舍Ⅰ的区别在于打家劫舍Ⅱ所有的房子是围在一圈的,即偷第一间房子就不能偷最后一间房。

这是小偷如果选择偷第一个房间,最后一间就不能偷。同样的:小偷不偷第一间,那么最后一间房间就可以偷。所以小偷只要比较偷第一间与不偷第一间哪个收益更高就行了。

用dp[i]代表第i个房间的最大收益。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f5Ohznd5-1621212255026)(动态规划-----打家劫舍.assets/image-20210514173432196.png)]

小偷选择偷第0间:第0间:最大收益为1。第1间:最大收益为3,第2间:3,第三间:6。由于小偷选择偷了第0个房间,所以最后一个房间小偷不能偷。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NS9iCnDP-1621212255029)(动态规划-----打家劫舍.assets/image-20210514173851875.png)]

同样的,小偷选择不偷第0间:第1间:3,第2间:3,第3间:6,第4间:103

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hPy44soz-1621212255031)(动态规划-----打家劫舍.assets/image-20210514173856632.png)]

代码:

class Solution {
    public int rob(int[] nums) {
       	if(nums.length<2){
            return nums[0];
        }
        if(nums.length<3){
            return Math.max(nums[0],nums[1]);
        }
        int ra = robInternal(nums,0,nums.length-1);
        int rb = robInternal(nums,1,nums.length);
        return Math.max(ra,rb);
    }
    
    private int robInternal(int[] nums,int begin,int end){
    	int[] dp = new int[nums.length];
        dp[begin] = nums[begin];
        dp[begin+1] = Math.max(nums[begin],nums[begin+1]);
        for(int i = begin+2;i<end;i++){
            dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[end-1];
    }
}

打家劫舍Ⅲ

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

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 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.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

房子摆成了二叉树,偷父节点,就不能偷子节点,但可以偷孙子节点。

对于每个节点,最好的情况是有2个子节点和4个孙子节点。儿子又有儿子和孙子,无穷尽也。

不能简单的把这道题看成层次遍历隔行求和。

对于每个节点来说,他都可能是一个爷爷节点:既有儿子也有孙子。对于这个节点:需要考虑的情况就是:偷爷爷+4个孙子还是偷2个儿子。

那么我们也可以记录每个节点的最大收益即可。只要从根节点开始递归,以此到叶子节点,就能知道每个节点的最大收益。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
		if(root==null){
			return 0;
		}
		
		int ra = root.val;
		
		if(root.left!=null){
			ra+=rob(root.left.left)+rob(root.left.right);
		}
		if(root.right!=null){
			ra+=rob(root.right.left)+rob(root.right.right);
		}
		int rb = rob(root.left)+rob(root.right);
          return Math.max(ra, rb);
    }
}

注意:rob()方法返回的是每个节点的最大收益。

由于在计算儿子的时候,曾经的孙子会变成儿子的儿子在被计算一遍。孙子当爷爷的时候,又会再被计算一边。所以可以使用Map去存储每个计算过的节点,减少计算的次数。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
		return robInternal(root,new HashMap<>());
    }
    
    private int robInternal(TreeNode root,Map<TreeNode,Integer> map){
    	if(root==null){
			return 0;
		}
    	if(map.containsKey(root)){
    	   return map.get(root);
    	}
        int ra = root.val;
    	if(root.left!=null){
			ra+=robInternal(root.left.left,map)+robInternal(root.left.right,map);
		}
		if(root.right!=null){
			ra+=robInternal(root.right.left,map)+robInternal(root.right.right,map);
		}
		int rb = robInternal(root.left,map)+robInternal(root.right,map);
		int res = Math.max(ra, rb);
		map.put(root,res);
		return res;
    }
}

以上是关于动态规划--打家劫舍的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode刷题笔记-动态规划-day3

写写代码系列033:打家劫舍(动态规划)

动态规划打家劫舍(III)

leetcode专项 动态规划入门

leetcode专项 动态规划入门

leetcode专项 动态规划入门