当前位置: 首页 > news >正文

代码随想录算法训练营第60期第四十三天打卡

        大家好,前几天我们已经学习完了动态规划里面的背包问题,我们主要了解的是0-1背包和完全背包,今天我们继续我们的动态规划,今天要讲的应该还是一个动态规划的一个很重要的问题就是打家劫舍问题,听说这个系列不算难,我们尽量一次拿下。

第一题对应力扣编号为198的题目打家劫舍

         我们就直接看看题目要求:

        题目说的是其实就是我们不能连续偷现金否则就会触发报警装置,这样题目让我们求我们可以偷到的最高金额,我们就一起看看这道题的解题思路:其实需要仔细考虑的一个问题就是这个房间我们能不能偷,我们应该偷哪一个房间,其实我们可以很清楚地了解,我们当前的房间能不能偷其实取决于我们前面的两个房间有没有偷的状态,如果我们前一个房间被偷了的话我们这个房间就不能偷了,如果前一个房间没有被偷前一个房间的前一个被偷的话那我们这个房间是可以偷的,那这样很明显这道题目我们就可以使用动态规划来解决,那我们就使用动规五部曲:

      第一步确定dp数组的含义,dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]

      第二步确定递推公式,决定dp[i]的因素就是第i房间偷还是不偷。如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2] 加上第i房间偷到的钱。如果不偷第i房间,那么dp[i] = dp[i - 1],即考 虑i-1房,这个就是题目的关键所在,如果偷当前的房间那我的前一个房间是一定不能偷的,其实如果我们前一个房间没有被偷的话我们是考虑可以偷这个房间的,注意仅仅是考虑并不一定要偷,综上我们就可以得到我们的递推公式:即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])。

       第三步是dp数组的初始化:从递推公式dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);可以看出,递推公式的基础就是dp[0] 和 dp[1],从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1])。这个是需要考虑清楚的,这个很重要,理解了这个离解决这道题目就不远了。

        第四步是确定遍历顺序:dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历!这个不难理解,我们以前的背包问题都是了解过的。

       那接下来我们已经走完了动规五部曲,我们就可以尝试给出代码:

class Solution {
public:int rob(vector<int>& nums) {//这个是毋庸置疑的,都没有房间去哪里偷钱if (nums.size() == 0) return 0;if (nums.size() == 1) return nums[0];//到第i个房间的所偷最大金额是dp[i]vector<int> dp(nums.size());//初始化dp数组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[nums.size() - 1];   }
};

        注意我们是考虑下标的,所以我们最后返回的是dp[nums.size() - 1],这个大家务必注意。

 第二题对应力扣编号为213的题目打家劫舍II

        这道题应该是上一道题目的变式题,那有什么不一样的,我们还是先看一下题目要求:

        注意这道题目房间是围成一圈的,这是与上一道题目不一样的地方,还是不能偷相邻的两个房间,那这道题目我们应该如何考虑:那我们首先得考虑一下数组成环的情况:

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

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

第三种情况考虑包含尾元素,不包含首元素:

       情况三,虽然是考虑包含尾元素,但不一定要选尾部元素! 其实大家也可以看出来连成环以后我们首尾是不可以一起偷的,所以我们才会有了这三种情况,对于情况三,取nums[1] 和 nums[3]就是最大的。其实大家可以看到我们的情况2和情况3已经包含了情况1,分析到这里其实这道题目与我们上面的题目就是一样的了,那我们就开始动归五部曲就可以,具体的动规五部曲我就不写了,与上一道题目是一样的,只不过这里需要考虑两种情况就是情况2与情况3最后我们取最大值就可以了:

class Solution {
public:int rob(vector<int>& nums) {if (nums.size() == 0) return 0;if (nums.size() == 1) return nums[0];int result1 = robchange(nums, 0, nums.size() - 2);int result2 = robchange(nums, 1, nums.size() - 1);return max(result1, result2);}int robchange(vector<int> &nums, int start, int end){if (start == end) return nums[start];vector<int> dp(nums.size());//初始化dp[start] = nums[start];dp[start + 1] = max(nums[start], nums[start + 1]);for (int i = start + 2; i < nums.size(); ++i){dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);} return dp[end];}
};

第三题对应力扣编号为337的题目打家劫舍 III

        这又是一道变式题,而且这道题目还引出了我们以前的二叉树,大家是不是以前二叉树的题目浮现脑海,我们看一下这道题目的意思:

        其实就是根节点开始,还是两个相邻的是不可以一起偷的,那我们就看一下这道题目我们应该如何思考:这道题目就有难度了,我们得思考一下我们应该是使用前中后序的哪种遍历方式呢?如果这个问题想不明白本题很可能就想不出来了,本题一定是要后序遍历,因为通过递归函数的返回值来做下一步计算。如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子。这是大家需要理解的一个难点。

       其实我以前了解过树形dp,这个其实可以算是树形dp了,这不就是在树上进行递推吗?这就是一个树形dp的题目,当然树形dp其实是很难的,这道题我们要融合递归三部曲与动规五部曲来解决:

       第一步确定递归函数的参数和返回值,这里我们要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组。所以dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。

       第二步确定终止条件,在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回。

      第三步是确定遍历顺序,首先明确的是使用后序遍历。 因为要通过递归函数的返回值来做下一步计算。通过递归左节点,得到左节点偷与不偷的金钱,通过递归右节点,得到右节点偷与不偷的金钱。

      第四步确定单层递归的逻辑,如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0], 如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);这个大家需要理解,就是我目前只是考虑,究竟选择哪一样偷是最大值说了算。我们是持续递归求偷当前节点得到的金钱和不偷当前节点得到的金钱,那这样我们就可以尝试写出代码:

/*** 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:int rob(TreeNode* root) {vector<int> result = robTree(root);return max(result[0], result[1]);}vector<int> robTree(TreeNode *cur){if (cur == NULL) return vector<int> {0, 0};vector<int> left = robTree(cur -> left);vector<int> right = robTree(cur -> right);//首先是看偷cur的情况(左右子树都不能偷了)int val1 = cur -> val + left[0] + right[0];int val2 = max(left[0], left[1]) + max(right[0], right[1]);return {val2, val1};}
};

       这道题目首先得搞清楚我的各个数组的含义,0表示不偷,1表示偷,这样的话我们取最大值就可以找到我们可以偷的最大金额,大家首先得搞清楚我们要使用后续遍历。

今日总结

       我们今天的题目其实是一个背景三种模式,一个是线性的数组,一个是环,还有一个是二叉树,考察的很全面,大家还是注意多思考,一定要搞清楚和dp数组的含义,那我们就到这里,下一次博客再见!

相关文章:

  • 高级学习算法(神经网络 决策树)
  • 如何用JAVA手写一个Tomcat
  • Web服务器(Tomcat)
  • LabVIEW风机状态实时监测
  • tomcat知识点
  • labview硬件部分——压力测量
  • LabVIEW中EtherCAT从站拓扑离线创建及信息查询
  • linux中安装jdk(Java环境),tomcat
  • labview硬件部分——温度测量
  • 【容易坑】mybatis中使用if标签比较两个字符串是否相等
  • Access链接Azure SQL
  • ubuntu 搭建FTP服务,接收部标机历史音视频上报服务器
  • 算法C++最大公约数
  • ArcGIS Pro 3.4 二次开发 - Arcade
  • Java 安全SPEL 表达式SSTI 模版注入XXEJDBCMyBatis 注入
  • 搭载1000nits激光显示技术,海信电视探索X1系列发布
  • PCB设计实践(二十四)PCB设计时如何避免EMI
  • Debian重装系统后
  • 【Vue3】数据的返回和响应式处理(ref reactive)
  • 2025年 PMP 6月 8月 专题知识
  • 广告公司微网站建设/网络营销的重要性
  • 企业公司网站制作/上海百度推广电话客服
  • 企业做网页还是网站/关键词seo优化排名公司
  • 企业所得税和增值税的区别/seo整站优化哪家好
  • 武汉手机网站设计如何/网站seo查询工具
  • 怎么快速建动态网站/网络营销软件下载