【动态规划】-6.打家劫舍问题

1.198. 打家劫舍

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

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

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

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

解题思路:

本题实际就是典型的动态规划问题,沿街的房屋依次决定每一家偷还是不偷为一个阶段,用dp[i]表示前i家获取的最高金额,第i阶段的决策就是两种:偷、不偷。则不难写出以下状态转移方程:

  dp[i]=max(dp[i-1],dp[i-2]+nums[i])

class Solution {
public:
    int rob(vector& nums) {
        if(nums.size()==0) return 0;
        //典型的动态规划问题,dp[i]=max(dp[i-1],dp[i-2]+nums[i])
        //每个阶段确定一家偷还是不偷,所以决策就是偷和不偷两种
        int len =nums.size();
        vector dp(len+1,0);
        dp[0]=0;
        dp[1]=nums[0];

        for(int i=2;i<=nums.size();i++){
            dp[i] = max(dp[i-1],dp[i-2]+nums[i-1]);
        }
        return dp[len];
    }
};

2.213. 打家劫舍 II

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

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

示例 1:

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

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

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

解题思路:

动态规划的关键就是找通项公式,这个题的通项公式很简单,

会DP应该都能想到:dp[i] = MAX(dp[i-1], dp[i-2] + nums[i])

基本思路当然还是动态规划,可以划分成两种情况来做,以第一家是否被偷为依据分成两个动态规划问题,如果第一家偷,那么从第一家到第n-1家求最大值(因为意味着最后一家一定不能偷);如果第一家不偷,那么从第2家到第n家求最大值。最后再比较两种情况的最大值即可。

class Solution {
public:
    int rob(vector& nums) {
        //分两种讨论:偷第一家,去掉最后一家
        //偷最后一家,去掉第一家
        //把打家劫舍变成无环的
        if(nums.size()==0) return 0;
        if(nums.size()==1) return nums[0];

        int len=nums.size();

        vector dp1(nums.size()+1,0);
        vector dp2(nums.size()+1,0);

        dp1[0]=0;
        dp2[0]=0;
        dp1[1]=nums[0];//偷第一家
        dp2[1]=0;//偷最后一家
        for(int i=2;i<=nums.size();i++){
            dp1[i] = max(dp1[i-1],dp1[i-2]+nums[i-1]);
            dp2[i] = max(dp2[i-1],dp2[i-2]+nums[i-1]);
        }

        return max(dp1[len-1],dp2[len]);
    }
};

3.337. 打家劫舍 III

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

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

示例 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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:

实际上,有上一题的经验,我们不难找到规律:这里可以按根结点是否打劫分为两种情况,如果根结点被打劫,那么意味着它的子节点都不能打劫,需要直接打劫其孙子辈节点;如果根结点不打劫,那么可以考虑其左右孩子。同时,由于树的特性,左右子树都是子问题,因此实际上就是两种情况分别递归

c++递归超时

class Solution {
    public int rob(TreeNode root) {
        /*
        按根偷不偷,分两种情况,分别递归
        */
        if(root==null)
            return 0;
        int res1=0;
        //根在结果中
        res1+=root.val;
        if(root.left!=null)
            res1+=(rob(root.left.left)+rob(root.left.right));
        if(root.right!=null)
            res1+=(rob(root.right.left)+rob(root.right.right));
        
        //根不在结果中
        int res2=rob(root.left)+rob(root.right);
        return Math.max(res1,res2);
    }
}

 

你可能感兴趣的:(算法导论,经典习题,打家劫舍,力扣,动态规划)