LintCode刷题——打劫房屋IIIIII

Posted Revenent

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LintCode刷题——打劫房屋IIIIII相关的知识,希望对你有一定的参考价值。

打劫房屋I:

题目内容:

假设你是一个专业的窃贼,准备沿着一条街打劫房屋。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自动报警。给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,你最多可以得到多少钱 在不触动报警装置的情况下。

样例:

给定 [3, 8, 4], 返回 8.

挑战:

O(n) 时间复杂度 且 O(1) 存储。

算法分析:

前提:对于某一间房子i,如果盗贼要打劫该房子,则房间对序号为i-1的房子(即前一所房子)盗贼不能进行打劫才能保证系统不报警。因此,很容易得出动态规划的表达式:

  ①建立一个数组DP[],DP[i]用来表示盗贼打劫到第i所房子时所能获得的最大金额数;

  ②根据前提的描述,盗贼不打劫当前房子,则DP[i] = DP[i-1];否则DP[i] = DP[i-2] + A[i];

因此DP[i] = max{ DP[i-1], DP[i-2] + A[i]};

代码:

public class Solution {
    /*
     * @param A: An array of non-negative integers
     * @return: The maximum amount of money you can rob tonight
     */
    /*时间开销为O(n) 空间开销为O(n);
    public long houseRobber(int[] A) {
        // write your code here
        if(A.length==0){
            return 0;
        }
        long[] DP = new long[A.length+1];
        DP[1] = A[0];
        for(int i=1;i<A.length;i++){
            DP[i+1] = Math.max(DP[i-1]+A[i],DP[i]);
        }
        return DP[A.length];
    }*/
    //空间开销为O(1);
    public long houseRobber(int[] A) {
        if(A.length==0){
            return 0;
        }
        if(A.length==1){
            return A[0];
        }
        long DP_i_1 = A[1];
        long DP_i_2 = A[0];
        for(int i=2;i<A.length;i++){
            long temp = DP_i_1;
            DP_i_1 = Math.max(temp,DP_i_2+A[i]);
            DP_i_2 = temp;
        }
        return DP_i_1;
    }
}

打劫房屋II:

题目内容: 

在上次打劫完一条街道之后,窃贼又发现了一个新的可以打劫的地方,但这次所有的房子围成了一个圈,这就意味着第一间房子和最后一间房子是挨着的。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自动报警。给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,你最多可以得到多少钱 在不触动报警装置的情况下。

注意事项:

这题是House Robber的扩展,只不过是由直线变成了圈。

样例:

给出nums = [3,6,4], 返回 6, 你不能打劫34所在的房间,因为它们围成一个圈,是相邻的。

算法分析:

本题在打劫房屋I的基础上增加了一个条件就是第一所房子与最后一所房子也不能兼得,其实本质并没有发生变化。在I中我们的建立的动态规划数组为DP[i]表示盗贼打劫到第i所房子获利的最大值,因此我们同样可以建立两个数组DP_1[] 和 DP_2[] 分别用来记录打劫区间为[1, n-1] 与 [2,n]的获利情况。这是因为第一所房子与最后一所房子不能够相邻,因此将其分为两个区间[1,n-1]与[2,n]后则就将问题化为了直线的打劫问题,也就可以运用I中的方法求解。

代码:

public class Solution {
    /*
     * @param nums: An array of non-negative integers.
     * @return: The maximum amount of money you can rob tonight
     */
    public int houseRobber2(int[] nums) {
        // write your code here
        if(nums.length==0||nums.length==2||nums==null){
            return 0;
        }
        if(nums.length==1){
            return nums[0];
        }
        int[] DP_1 = new int[nums.length-1];
        int[] DP_2 = new int[nums.length-1];
        //不打劫最后一所房子则从第一所房子开始打劫
        for(int i=0;i<nums.length-1;i++){
            if(i==0){
                DP_1[i] = nums[0];
            }
            if(i==1){
                DP_1[i] = Math.max(nums[1],DP_1[0]);
            }
            if(i>1){
                DP_1[i] = Math.max(DP_1[i-2]+nums[i],DP_1[i-1]);
            }
        }
        //打劫最后一所房子则从第二所房子开始打劫
        for(int i=1;i<nums.length;i++){
            if(i==1){
                DP_2[i-1] = nums[1];
            }
            if(i==2){
                DP_2[i-1] = Math.max(nums[2],DP_2[0]);
            }
            if(i>2){
                DP_2[i-1] = Math.max(DP_2[i-3]+nums[i],DP_2[i-2]);
            }
        }
        return DP_1[nums.length-2]>DP_2[nums.length-2]?DP_1[nums.length-2]:DP_2[nums.length-2];
    }
}

打劫房屋III:

题目描述:

在上次打劫完一条街道之后和一圈房屋之后,窃贼又发现了一个新的可以打劫的地方,但这次所有的房子组成的区域比较奇怪,聪明的窃贼考察地形之后,发现这次的地形是一颗二叉树。与前两次偷窃相似的是每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且当相邻的两个房子同一天被打劫时,该系统会自动报警。算一算,如果今晚去打劫,你最多可以得到多少钱,当然在不触动报警装置的情况下。

样例:

  3
 / 2   3
 \   \ 
  3   1

窃贼最多能偷窃的金钱数是 3 + 3 + 1 = 7.

    3
   /   4   5
 / \   \ 
1   3   1

窃贼最多能偷窃的金钱数是 4 + 5 = 9.

算法分析:

打劫房屋I、II均为对数组的动态规划处理,但本题是要求对二叉树进行一个动态规划处理。对于本题,首先要了解怎样是相邻:有直接连接的节点之间算相邻节点,即父节点与亲子节点;

对于二叉树中的某一个节点i,它也有偷与不偷这两个选项,若偷,则两个子节点不能偷;否则,两个子节点可以偷。与I、II不同的是,I、II中对当前的房屋i,偷与不偷仅需把其中的较大者保留下来进行,因为后续的结果均是建立在前者为最优解的基础上进行的,而且DP[i] = max{DP[i-1],DP[i-2]+A[i]}一定能保证不发生冲突;但是对于二叉树,当前节点i并不能那么方便的找到其子节点的子节点的最优解,因此并不能同I、II那样仅记录最优解。对于二叉树的节点而言,其最容易访问到的就是它的两个子节点。因此,可以建立一个大小为2的一维数组DP,其中DP[0]用来记录偷当前节点所能获利值,DP[1]用来记录不偷当前的值所能获利的值。因为二叉树由根节点开始进行发散,因此可以用后序遍历的方式最终返回一个二维数组。动态规划表达式如下:

  对于某一个节点node,若偷:DP[0] = left[1] + right[1] + node.val; 若不偷:DP[1] = max{left[0],left[1]} + max{right[0],right[1]};

代码:

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int x) { val = x; }
 * }
 */


public class Solution {
    /*
     * @param root: The root of binary tree.
     * @return: The maximum amount of money you can rob tonight
     */
    public int[] postTrack(TreeNode node){
       if(node==null){
           int[] result = new int[]{0,0};
           return result;
       }
       int[] result = new int[2];
       int[] left = postTrack(node.left);
       int[] right = postTrack(node.right);
       //偷当前结点
       result[0] = left[1] + right[1] + node.val;
       //不偷当前节点 
       result[1] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);
       return result;
    }
    public int houseRobber3(TreeNode root) {
        // write your code here
        int[] result = postTrack(root);
        return Math.max(result[0],result[1]);
    }
}

 

  

  

以上是关于LintCode刷题——打劫房屋IIIIII的主要内容,如果未能解决你的问题,请参考以下文章

LintCode 534. 打劫房屋 II

打劫房屋III

算法-打劫房屋III(深搜)

Leetcode刷题Python337. 打家劫舍 III

lintcode-515-房屋染色

lintcode - 房屋染色