打家劫舍问题

今天我们来聊聊leetcode上的“打家劫舍”系列问题。

我们可以回顾一下之前说过的“股票问题”,然后同样的我们用一个模板来团灭
“打家劫舍”系列问题。

这次当然还是【动态规划】,之前有想过用“贪心”来做,后来发现无法实现对所有情况的穷举遂转向动态规划,之前有说过动态规划其实是优化了的穷举法,比较适合解此类看似情况繁多但同时又联系紧密的问题,简直是屡试不爽。下面我们开始步入正题。

对于所有动态规划而言,都逃不开找【状态】和做【选择】,此题的状态就是摆在你面前的一个个房屋,你要做出的选择就是“抢”或“不抢”;是不是很简单?那么关键就在于如何转化为代码了;这里我们直接用dp数组来解决问题。
我们假设dp[i]的含义为在前i个房屋我们能打劫到的最大钱数,那么下面我们思考状态转移:dp[i]是由dp[i-1]做了相应选择转化(抢或者不抢)而来的,如果抢了第
i-1个房屋,那么就不能抢第i间,此时dp[i]=dp[i-1];如果不抢第i-1间房屋(此时dp[i-1]=dp[i-2]),那么就可以抢第i间房屋,那我们如何确定抢不抢呢?很简单,就看那个能够得到最多的前就好了,综上:dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
所以用循环来穷举所有状态如下:

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

对于以上代码可继续优化节省空间开支


class Solution {
     
public:
    int rob(vector<int>& nums) {
     
        int n=nums.size();
        if(n==0)return 0;
        //base case
        //i=-1时,没有房子可以抢,故dp_i=0,
        //且再往前就更没有房子可以抢,所以
        //dp_i_1=dp_i_2=0;
        int dp_i=0,dp_i_1=0,dp_i_2=0;
        for(int i=0;i<n;i++){
     
            dp_i=max(dp_i_1,dp_i_2+nums[i]);
            dp_i_2=dp_i_1;
            dp_i_1=dp_i;
        }
           return dp_i;
    }
};

下面继续说最难的那个将二叉树融入此系列问题的同时也是最难的一个“打家劫舍”问题:
思路同样是找【状态】和【做选择】,且都是和之前一样的思路,只不过状态转移的方程不同而已。
按题目要求也就是:如果抢了根节点,就不能再抢其孩子节点,但可以同时抢一个根节点的两个孩子节点。
下面贴上相关代码,这部分代码的精妙之处在于很巧妙地用了一个二维数组来表示抢或者不抢的【选择】,大大简化了代码,非常漂亮。

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

/* 返回一个大小为 2 的数组 arr
arr[0] 表示不抢 root 的话,得到的最大钱数
arr[1] 表示抢 root 的话,得到的最大钱数 */
int[] dp(TreeNode root) {
     
    if (root == null)
        return new int[]{
     0, 0};
    int[] left = dp(root.left);//表示以其左孩子为根节点能得到的最大钱数
    int[] right = dp(root.right);//表示以其右孩子为根节点能得到的最大钱数
    // 抢,下家就不能抢了
    int rob = root.val + left[0] + right[0];
    // 不抢,下家可抢可不抢,取决于收益大小
    int not_rob = Math.max(left[0], left[1])
                + Math.max(right[0], right[1]);
    //因为可以同时抢左右孩子,所以是左右孩子做出选择后最大值之和作为此次“不抢”所能获得的最大收益
    return new int[]{
     not_rob, rob};
}

c++版本

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
     
public:
    int rob(TreeNode* root) {
     
        vector<int>res=dp(root);
        return max(res[0],res[1]);
    }
    vector<int> dp(TreeNode* root){
     
       if(root==NULL)
       return vector<int>{
     0,0};
        vector<int>left=dp(root->left);
        vector<int>right=dp(root->right);
        int rob=root->val+left[0]+right[0];
        int not_rob=max(left[0],left[1])+max(right[0],right[1]);
        return vector<int>{
     not_rob,rob};//注意此处not_rob与rob的位置
        //因为我们定义的是arr[0]是不抢,arr[1]代表抢,所以第一个元素必须是
        //not_rob,否则会得到错误的答案。
    }
};

你可能感兴趣的:(算法,动态规划,二叉树,数据结构)