● 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]的过程先推导一下。

相关推荐
zzzhpzhpzzz13 分钟前
设计模式——观察者模式
算法·观察者模式·设计模式
Mr__vantasy24 分钟前
数据结构(初阶6)---二叉树(遍历——递归的艺术)(详解)
c语言·开发语言·数据结构·算法·leetcode
敲键盘的老乡27 分钟前
堆优化版本的Prim
数据结构·c++·算法·图论·最小生成树
码农多耕地呗30 分钟前
trie树-acwing
数据结构·c++·算法
奥利奥冰茶31 分钟前
Linux下通过DRM操作屏幕,发生行对齐 (stride)问题
算法
建模忠哥小师妹36 分钟前
2024亚太杯C题宠物行业及相关产业的发展分析和策略——成品参考思路模型代码
算法
daily_23331 小时前
数据结构——小小二叉树第三幕(链式结构的小拓展,二叉树的创建,深入理解二叉树的遍历)超详细!!!
数据结构·c++·算法
浦东新村轱天乐2 小时前
神经网络反向传播算法公式推导
神经网络·算法·机器学习
SUN_Gyq2 小时前
什么是 C++ 中的模板特化和偏特化? 如何进行模板特化和偏特化?
开发语言·c++·算法
码上一元2 小时前
【百日算法计划】:每日一题,见证成长(026)
算法