代码随想录算法训练营Day34 | 198.打家劫舍、213.打家劫舍II、337.打家劫舍III

LeetCode198.打家劫舍

198. 打家劫舍 - 力扣(LeetCode)

1.思路

当前房屋是否要偷取决于前一个房屋和前两个房屋的情况,所以当前房屋的状态收到前面房屋状态的依赖。

动态规划五部曲:

1. dp[i]:从当前房屋 i 及以前的房屋中可以偷窃的最大金额;

2. 如果偷第 i 个房间,那么就找到 i-2 以内的房间能偷窃的最大金额,再加上当前房间的价值,即 dp[i - 2] + nums[i];如果不偷第 i 个房间,那么当前 dp[i] 就取决于 i-1 以内的房间的最大金额,即 dp[i - 1],然后取这两种情况中的最大值。

所以递推公式:dp[i] = max(dp[i - 1],dp[i - 2] + nums[i]);

3. 由递推公式可以看出,递推公式的基础就是 dp[0],dp[1],所以根据 dp[i] 的定义,dp[0] = nums[0],dp[1] = max(nums[0],nums[1]);

4. dp[i] 依赖于 dp[i - 1] 和 dp[i - 2],所以是从前往后遍历;

5. nums = [2,7,9,3]

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()<2) return nums[0];
        vector<int>dp(nums.size(),0);
        dp[0]=nums[0];
        dp[1]=max(nums[0],nums[1]);
        for(int i=2;i<nums.size();i++){
            dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[nums.size()-1];
    }
};

2.复杂度分析

时间复杂度:O(n)
空间复杂度:O(n)

3.思考

这道题就不是背包问题了,我一开始还想着用背包去解,还用了两层 for 循环。不过思路大体是一致的,还是五部曲。要解决这道题,首先要想清楚 dp[i] 的定义,其次就是当前 dp[i] 能通过哪些情况推出,然后就是根据递推公式来确定遍历顺序。

4.Reference:198.打家劫舍 | 代码随想录


LeetCode213.打家劫舍II

213. 打家劫舍 II - 力扣(LeetCode)

1.思路

这道题和 打家劫舍1 不同,这里连成了环,所以我们要考虑首元素和尾元素的取舍。

第一种:考虑包含首元素,不包含尾元素

第二种:考虑包含尾元素,不包含首元素

这里不需要考虑只取中间,不取两边的情况,因为情况一和情况二已经包含了这一情况。

其余部分和打家劫舍1一样,这里就只用考虑两种情况,分别求解最大值,然后取最大值即可。

class Solution {
public:
    int maxValue(vector<int>& nums,int l,int r){
        if(l>=r) return nums[l];
        vector<int>dp(nums.size(),0);
        dp[l]=nums[l];
        dp[l+1]=max(nums[l],nums[l+1]);
        for(int i=l+2;i<=r;i++){
            dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[r];
    }
    int rob(vector<int>& nums) {
        if(nums.size()==0) return 0;
        if(nums.size()<2) return nums[0];
        int n1 = maxValue(nums,0,nums.size()-2);    // 情况一
        int n2 = maxValue(nums,1,nums.size()-1);    // 情况二
        return max(n1,n2);
    }
};

2.复杂度分析

时间复杂度:O(n)

空间复杂度:O(n)

3.思考

把线性关系改成环形关系,就需要考虑头和尾的取舍情况,不能既顾头又顾尾,所以这里要考虑带头不带尾,带尾不带头的情况,而不带头不带尾的情况则不用考虑,因为已经包含在上述两种情况中。

4.Reference:213.打家劫舍II | 代码随想录


LeetCode337.337.打家劫舍III

337. 打家劫舍 III - 力扣(LeetCode)

1.思路

这里定义一个长度为 2 的数组 dp[2],dp[0] 记录不偷当前节点所得的最大金额,dp[1] 记录偷当前节点所得到的最大金额。

1. 要求每一个节点偷与不偷的两种状态,所以要返回数组,即相当于返回每一个节点的 dp 数组;

2. 在遍历的过程中,如果遇到空节点的话,无论偷还是不偷都是0,所以就返回 {0,0},相当于 dp 数组的初始化;

3. 后序遍历,先得到左节点偷与不偷的最大金额,再得到右节点偷与不偷的最大金额,最后处理当前节点偷与不偷的情况;

4. 如果偷当前节点,那么左右孩子都不能偷,所以 val1 = root->val + leftdp[0] + rightdp[0];如果不偷当前节点,那么就分别选取左孩子和有孩子两种情况中的最大金额,然后作和,即 val0 = max(leftdp[0],leftdp[1]) + max(rightdp[0],rightdp[1]);然后返回当前节点的状态 {val0,val1};

5. 举例 dp 数组,最后头结点就是取下标 0 和 下标 1 的最大值就是偷得的最大金钱。

class Solution {
public:
    // 长度为2的数组,0:不偷,1:偷
    vector<int> robtree(TreeNode* root){
        if(root==NULL) return {0,0};
        vector<int>leftdp = robtree(root->left);
        vector<int>rightdp = robtree(root->right);
        // 不偷当前节点,则偷左右孩子的最大值
        int val0 = max(leftdp[0],leftdp[1])+max(rightdp[0],rightdp[1]);
        // 偷当前节点,则不能偷左右孩子
        int val1 = root->val+leftdp[0]+rightdp[0];
        return {val0,val1};
    }
    int rob(TreeNode* root) {
        vector<int>res = robtree(root);
        return max(res[0],res[1]);
    }
};

2.复杂度分析

时间复杂度:O(n) - 每个节点只遍历了一次
空间复杂度:O(log n)

3.思考

这道题是树形 dp 的入门题目,也就是要在树上进行递推公式的推导,但是平时习惯了在一维数组上推导公式,一下子换成了树就没什么思路了。

4.Reference:337.打家劫舍 III | 代码随想录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值