● 198.打家劫舍 ● 213.打家劫舍II ● 337.打家劫舍III

● 198.打家劫舍

动规五部曲。

1、dp[j]含义。前j个房屋偷到的金额之和最大是dp[j]。

2、递推公式。递推公式要得出dp[i],就是要确定第i个房屋是否打劫,那么也跟之前的背包问题一样,放与不放,对应的是两种结果,我们只需要取这两种结果的最大值就行,而不需要用if-else语句来判断放和不放两种情况。

第i个房屋不被打劫,肯定前一个房屋要被打劫,所以此时dp[i]=dp[i-1]。

第i个房屋被打劫,肯定前一个房屋不能被打劫,所以dp[i]=dp[i-2]+nums[i]。因为i-1个房屋不考虑。

因此dp[i]是这两个数的最大值:dp[i]=max(dp[i-1], dp[i-2]+nums[i])

3、初始化。初始化要根据dp[j]含义来,上面递推公式涉及到前两个房屋,所以下标为0和 为1的房屋都要初始化。前1个房屋偷到的金额之和是nums[0],第2个房屋偷到的金额之和是max(nums[0],nums[1])。

或者说:只有1个房屋的话,就偷这一家,有2个房屋的话,偷其中金额大的那一家。

4、遍历顺序。到达第i个房屋,dp[i]需要根据前两个房屋的dp来,所以是正序遍历。

5、打印dp数组。

代码如下:

cpp 复制代码
class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==1)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-2]+nums[i],dp[i-1]);
        }
        return dp.back();
    }
};

● 213.打家劫舍II

和上一题的区别,主要在于首元素、尾元素要不要考虑。上一题就是首尾元素以及中间的都考虑了,这道题的话首元素或者尾元素最多只有一个被选择,所以有下面三种情况:

情况1。首尾不选择,只考虑中间的。

情况2。

情况3。

是把这几种情况的最大金额分别算出来然后取最大值。

又因为情况2和情况3共有的部分包含了情况3,所以把情况2、情况3的最大金额分别算出来然后取最大值。

算最大值的过程就是和上一题一样,所以用两次上一题的过程,那么封装成一个函数。

代码如下。

cpp 复制代码
class Solution {
public:
    int subrob(vector<int>& nums,int a,int b){//只考虑[a,b]范围内的
        vector<int> dp(nums.size(),0);
        dp[a]=nums[a];  
        dp[a+1]=max(nums[a],nums[a+1]);
        for(int i=a+2;i<=b;++i){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[b];
    }
    int rob(vector<int>& nums) { 
        int n=nums.size();
        if(n==1)return nums[0];
        if(n==2)return max(nums[0],nums[1]);
        
        int rob1=subrob(nums,0,nums.size()-2);  //不考虑尾

        int rob2=subrob(nums,1,nums.size()-1);  //不考虑首
        return max(rob1,rob2);
    }
};

● 337.打家劫舍III
树形dp的入门题目。

要讨论某节点劫还是不劫,根据每个节点,我们只能知道它的两个孩子。如果这个节点选了,那么它的两个孩子就不能被打劫,这的两个孩子其实与上两道题的前一个房屋对应,所以只能后序遍历节点来解决,因为先要处理左右孩子,再根据孩子处理自己,左右中。

自己想不到的:不像之前的线性dp一样:一个dp数组,包含给定个数的元素。这里是遍历到一个节点了,就设置一个dp数组,这个数组里面2个元素:dp[0]是不选自己的最大值,dp[1]是选自己的最大值。主函数里面得到根节点的dp数组,然后返回根节点的dp[0]和dp[1]中的一个最大值就行,可见dp[0]和dp[1]是和前面dp[i]的max取值里面两个表达式含义相同。所以在递归体里面,我们要根据节点i左、右孩子的dp数组,得到i自己的的dp数组,一路向上,最终得到root的dp数组。

那么还是按递归的框架来,五部曲体现在递归体之中。

递归的返回类型应该是该节点的dp数组,告诉自己的父节点:不选自己打劫到的最大值和选择自己打劫到的最大值。父节点就根据左、右孩子的dp数组,来确定自己的dp数组。

就回到动规的递推公式,假设左孩子返回的dp数组是dpLeft,右孩子返回的dp数组是dpRight。首先看dp[1],选择了自己,那么左右孩子肯定不选,所以dp[1]应该等于不选左孩子打劫到的最大值dpLeft[0]加上不选右孩子打劫到的最大值dpRight[0],再加上自己的数,即:

cpp 复制代码
dp[1]=dpLeft[0]+dpRight[0]+root->val;

再看dp[0],自己不选择,孩子选不选都可以。刚开始觉得孩子得左右都选,才是最大,但是这只是局部的最大,而不是全局。比如下面的结构:

正确结果应该是4、3,那么节点1的孩子2就没有选择。

既然左、右孩子选不选都可以,那我们应该选择左、右孩子选和不选两种情况的最大值然后加起来。所以dp[0]应该等于左孩子dp[0]和dp[1]的最大值,再加上右孩子dp[0]和dp[1]的最大值。

cpp 复制代码
        dp[0]=max(dpLeft[1],dpLeft[0])+max(dpRight[0],dpRight[1]);//没选自己,左右孩子选与不选都可以

代码如下:

cpp 复制代码
/* Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> digui(TreeNode* root){
        if(!root)return vector<int>{0,0};//空节点
        vector<int> dp(2,0);//dp[0];dp[1]
        vector<int> dpLeft=digui(root->left);
        vector<int> dpRight=digui(root->right);
        
        dp[0]=max(max(dpLeft[1]+dpRight[1],dpLeft[0]+dpRight[1]),max(dpLeft[1]+dpRight[0],dpLeft[0]+dpRight[0]));//没选自己,可能左右孩子全选,也可能不选
        dp[1]=dpLeft[0]+dpRight[0]+root->val;//选自己,孩子肯定不选
        return dp;
    } 
    int rob(TreeNode* root) {
        vector<int> dp(2);
        dp=digui(root);
        return max(dp[0],dp[1]);
    }
};

打印dp数组。按照每个节点得到dp[0]、dp[1]的过程先推导一下。

相关推荐
算法歌者7 分钟前
[算法]入门1.矩阵转置
算法
林开落L22 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
远望清一色23 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
tyler_download24 分钟前
手撸 chatgpt 大模型:简述 LLM 的架构,算法和训练流程
算法·chatgpt
SoraLuna44 分钟前
「Mac玩转仓颉内测版7」入门篇7 - Cangjie控制结构(下)
算法·macos·动态规划·cangjie
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
鸽鸽程序猿1 小时前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
九圣残炎1 小时前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode
YSRM1 小时前
Experimental Analysis of Dedicated GPU in Virtual Framework using vGPU 论文分析
算法·gpu算力·vgpu·pci直通
韭菜盖饭2 小时前
LeetCode每日一题3261---统计满足 K 约束的子字符串数量 II
数据结构·算法·leetcode