算法训练营DAY47|198.打家劫舍、213.打家劫舍II 、337.打家劫舍III

news2024/5/18 19:58:46

这一期到了打家劫舍的专题,说是专题但实际上只有一期,而且只有三道题,我们把这三道题放在一起讲,第一道题简单一些,后两道略有不同方向上的难度。但总体来看第一次做可能有一点难想到思路,其实代码实现还是可以的。

198. 打家劫舍 - 力扣(LeetCode)https://leetcode.cn/problems/house-robber/

题目要求相邻两间房子不能同时遭到盗窃。根据这一条件能够想清如何写出递推公式,则是解题的关键。

dp数组的含义:dp【i】代表了考虑当前第i个位置,是否被偷盗,所能盗得的最大金钱数,注意这里仅仅是考虑这个位置房子是否被偷盗,但是并不是一定会盗窃该房屋,具体是否盗窃该房屋,由递推公式决定。

递推公式: 对于此时遍历到的位置i有两种状态,偷或者不偷,如果是偷那么一定是当前房子被偷获得的价值nums【i】加上当前位置向前数的第二个位置所能获得的最大价值(同样的这个位置也不一定被偷)也就是dp【i-2】,偷该房子的最大获得钱币变成了nums【i】+dp【i-2】,如果不偷该房子,那么该房子的前一个位置就可以被考虑进来是dp【i-1】,它们两个取最大值

dp【i】=max(dp【i-2】+nums【i】,dp【i-1】)。这就是递推公式

我们要时刻记得当前位置i只是被考虑进来偷或者不偷,在递推公式取最大值之前,不能确定该位置一定偷或者不偷。

dp数组的初始化:dp【0】也就是起始位置,我们可以这样假想,加入房子只有一间,那么一定是偷盗了,才能获得最大金钱,所以dp【0】=nums【0】,我们还得为dp【1】初始化,因为递推公式需要用到i的前两个位置,dp【1】=max(nums【0】,nums【1】)是这两个屋子取最大值偷,因为相邻房子不能同时偷,同样可以想成一共只有两个房子,我们考虑偷第一间还是第二间。其余的位置初始化什么数都可以,因为并不能影响最后的答案。

遍历顺序:从前到后,很自然的遍历顺序,并没有考究。

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==0)return 0;
        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[nums.size()-1];
    }
};

思路捋清了发现还是很简单的,但是第一次可能还是有部分想不明白,我刚做该题的思路是这样的:dp【1】初始化为nums【1】,然后最后比较的是偷第一间房屋和不偷哪个价值更大,这样做实际上是有弊端的例如测试用例为{2,1,1,2}时,这种情况是中间两间房子不偷盗,而两边偷盗获得的多。题目虽然要求相邻房子不能偷,但是这并不意味着我们一定要不停偷取才能获得最大金钱!!这就是曲解了dp数组的含义,dp【i】应是考虑i这个位置,而不是一定偷盗,这样想的才能解决这样的测试用例。

下面也给出错误的代码

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

相信对于上面的详细讲解,大家也对于这类问题的入门题目有了一定的了解,下面看一个进阶版本的。213. 打家劫舍 II - 力扣(LeetCode)icon-default.png?t=N0U7https://leetcode.cn/problems/house-robber-ii/

这道题的题目和上一道的唯一区别,在于房子围成一周,第一间房子与最后一间连接起来,并且也不能同时取。线性问题的数组,我们容易想到解题思路,那么环形的我们该如何思考呢?将该环形数组分为三种情况,我们可以不看两边的房屋,只考虑中间部分 ,这是一种情况,这样中间部分可以像上一道题的思路一样,很轻松做出来,但是两边房屋该怎么办呢?别急还有另外两种情况,不考虑第一个房屋,和不考虑最后一个房屋。实际上这两种情况都包含了第一种情况,由于我们只是考虑这些房屋是否被偷盗,第二三种方法比第一种方法考虑的范围还要远,所以肯定是包含在内,而又由于第一间房屋和最后一间房屋并不能同时放在一起考虑,因为它们成环相连,所以我们只需取得二三种方法中较大得到金钱的那一个,即为最后的答案。将两个范围带入第一个题的函数中去,取得两个最大金钱值,取较大一个即可,看代码实现。

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==1)return nums[0];
        if(nums.size()==0)return 0;
        int result1=fun(nums,0,nums.size()-2);
        int result2=fun(nums,1,nums.size()-1);
        return max(result1,result2);
    }
    int fun(vector<int>&nums,int start,int end){
        if(start==end)return nums[start];//防止数组大小等于2
        vector<int>dp(nums.size(),0);
        dp[start]=nums[start];dp[start+1]=max(nums[start],nums[start+1]);
        for(int i=start+2;i<=end;i++){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[end];
    }
};

代码实现是不是也很简单?但是想到思路可不是很容易,尤其对于没有做过很多环形数据的题人来说。特别说明:为什么函数实现里多了一个if(start==end)的判断,我们可能会想正常数组怎么可能传进来的的开始和结束指向同一个位置呢?这其实并不是我们传入错误,有些时候数据可能较短,比如只有两个数据的情况,这样的话按照我们上面的思路,就会出现start和end值一样的情况,我们就要直接返回一个数,因为如果这样再往下走会出现数组越界的情况。


这最后一道是二叉树和动态规划的交叉使用例题,也是树形dp的入门题目。这道题要求对于二叉树的使用要娴熟,而且还要穿插上动态规划的使用,第一次做同样也是有难度的例题。

337. 打家劫舍 III - 力扣(LeetCode)icon-default.png?t=N0U7https://leetcode.cn/problems/house-robber-iii/要求同样是相邻房屋不能被连续抢劫,所以就是取父节点就不能取两个子节点了!

前面的dp数组含义是一样的,不同的是递推公式需要注意一点。

我们要写递归函数来遍历整颗二叉树,需要两个dp数组分别代表左子树和右子树对于该节点偷或者不偷的两种情况分别对应的钱数。说一个误区,题目虽然是说一个父节点只有一个孩子节点,那为什么我们还需要两个dp呢?是因为我们不确定下一个子房子是左子树还是右子树上,所以我们要两个dp数组,而且也可能是一会像左一会向右,所以两边都需要记录,但是仅仅是需要每一个dp数组只有两个参数,dp【0dp【1】,因为每次递归是会向下遍历的,上一层由系统栈存储,不需要担心每一个节点的数据会丢失。如果当前节点,我们选择偷那么值就是root-> val+leftdp【0】+rightdp【0】,因为取这个节点子节点都不能取,如果不取该节点就是leftdp【1】+rightdp【1】,它们两个最后取最大值就可以了。

细心的盆友肯定会发现,leftdp和rightdp的状态在我们遍历当前节点时候,不是还不能确定下来吗?所以这也是说明我们的遍历顺序是从下往上的后序遍历,把下面的状态推给上一层,这样就可以实现上一层的数据处理依赖下一层的数据了。

class Solution {
public:
    int rob(TreeNode* root) {
        vector<int>result=fun(root);
        return max(result[0],result[1]);
    }
    vector<int>fun(TreeNode* root){
        if(root==NULL)return vector<int>{0,0};
        vector<int>left=fun(root->left);
        vector<int>right=fun(root->right);
        int val1=max(left[0],left[1])+max(right[0],right[1]);
        int val2=root->val+left[0]+right[0];
        return {val1,val2};
    }
};

后序遍历的方法,可以让我们从下向上传递状态转移,遇到空子树,返回的相当于取和不取都是0。最后到根节点往上返回根节点取和不取的两种金钱数,取最大。注意return返回的val1,和val2的顺序不能颠倒,他们是有确定的含义的,返回上一层时候需要调用下一层的两个状态,写反了一定会出问题。

最后,对于递归向上返回时是如何工作的,我希望大家也自行实现一下,不要只知道代码大概模板,就通过了,做递归类问题最重要的是要知道递归每一步是如何返回的,这样更有利于深刻理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193781.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百度、字节终于不再相互“抄袭”

文|智能相对论作者|佘凯文“百度和字节跳动&#xff0c;分道扬镳”乍一看挺标题党的&#xff0c;这两个互联网巨头从没在一起过&#xff0c;又何来“分道扬镳”之说&#xff1f;不急&#xff0c;且往下看。众所周知&#xff0c;当前国内互联网行业&#xff0c;早已不是当初啥也…

【链表->环形链表】

诸如环形链表的结构有&#xff1a;尾节点链接向各个节点的链表&#xff0c;也可链向自己&#xff0c;称为环形链表。只要链表中带有环&#xff0c;均可称为环形链表。下面通过一些例题来详细讲述环形链表&#xff1a;1.给你一个链表的头节点 head &#xff0c;判断链表中是否有…

python进阶——人工智能实时目标跟踪

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a;lqj_本人的博客_CSDN博客-微信小程序,前端,vue领域博主lqj_本人擅长微信小程序,前端,vue,等方面的知识https://blog.csdn.net/lbcyllqj?spm1000.2115.3001.5343 哔哩哔哩欢迎关注&…

Apollo planning之参考线平滑算法

Apollo studio 官网&#xff1a;Apollo开发者社区 (baidu.com) 目录 1 参考线的作用 2 参考线的数据结构 2.1 ReferenceLine的数据结构 2.2 ReferencePoint的数据结构 3 参考线处理流程 ​4 参考线平滑算法 4.1 算法分类 4.2 参考线平滑算法流程 4.2.1 AnchorPoint …

Learning C++ No.1

引言&#xff1a; 北京时间 2023/2/1/20:38&#xff0c;三天没写博客&#xff0c;打字量严重下滑&#xff0c;这两天是看剧时间&#xff0c;我发现看电视剧有时候还是非常的让人无法自拔的&#xff0c;一天追完一部30集的电视剧&#xff0c;我还是很佩服我自己的&#xff0c;现…

我愿称之为天花板的【Python自动化测试开发文档】—自动化测试开发平台实战

我愿称之为天花板的【Python自动化测试开发文档】—自动化测试开发平台实战 目录&#xff1a;导读 本文以下内容均适合这类人群 包含的模块&#xff1a; 第一章&#xff1a;Python 零基础入门 第二章&#xff1a; Web 应用框架 第三章&#xff1a;自动化平台开发 第四章…

React的学习笔记-(Bilibili天禹老师)

React的特点 采用组件化模式,声明式编码,提高开发效率和组件复用率在React Native中可以使用React语法进行移动端开发(IOS和Android)使用虚拟DOM优秀的Diffing算法,尽量减少与真实DOM的交互 babel用处 es6 > es5jsx > js 1.你好,react 注意引入顺序 <!DOCTYPE h…

图扑软件 | 虚拟电厂负荷控制系统可视化

前言 随着国家“双碳”及“构建以新能源为主体的新型电力系统”等目标的提出&#xff0c;清洁化、数字化越来越成为电力系统面临的迫切需求&#xff0c;负控系统的发展对电力营销现代化建设具有重要的意义。 负控管理系统是一个着眼于全面加强电力信息管理的&#xff0c;集负…

Query 聚类

为了提高阅读体验&#xff0c;请移步到&#xff1a;Query 聚类背景搜索系统优化长尾 query。想了解一下长尾 query 长什么样&#xff1f;大体上都有几类&#xff1f;最好能归类&#xff0c;一类一类处理。Query 数据源&#xff1a;包含“什么”&#xff0c;“怎么”&#xff0c…

儿童台灯怎么选对眼睛好?2023开学必买的儿童台灯

l 通过国家卫健委发布的数据——2020年儿童青少年总体近视率高达52.7% l 爱尔眼科视光研究所的数据——6岁儿童中45%已失去远视储备&#xff0c;6-10岁近视度数增长最快 l 孩童近视程度的发展之外&#xff0c;让人猝不及防 l 在光照环境中&#xff0c;能给孩子们提供最好的阅…

力扣468验证IP地址C++判断合法IP字符串

目录前言题目描述解题思路主功能函数分类大框架判断IPv4是否合法判断IPv6是否合法其余小边界条件(调试后得)完整代码前言 这是一道常见的笔试面试题,我找实习已经碰到两次了&#xff0c;和矩阵的乘法出现频率一样高&#xff0c;你校招要是全程没遇到可以过来打我;(这道题大厂面…

SAP IFRS 17 面向服务架构详细解析(包含分类账规则)

经过漫长的旅程,国际会计准则委员会 (IASB) 于 2017 年 5 月发布了 IFRS 17“保险合同”(IFRS 17)。IFRS 17 取代了 2004 年发布的 IFRS 4。总体目标是提供一个更加透明和全球签发保险合同的实体之间保险合同的统一会计模型。在 IFRS 17 标准发布三年后,IASB 于 2020 年 6 月…

Linux下tomcat服务器的html文件部署

本章介绍使用Xshall发布命令,远程操作Linux系统,在Linux上搭建Java程序部署环境 本文章只是简单介绍html文件的部署,不涉及连接数据库 1.安装JDK 使用yum安装openjdk与windows中的jdk虽然不同,但是功能相似,这里的yum相当与中央管理仓库,包含了各种各样的软件 列出jdk相关的…

【黄啊码】PHP结合vue导出excel乱码

在这之前我们先回顾以前用php导出excel&#xff0c;我直接写成方法在这里&#xff1a; public static function phpExcelList($field, $list, $title文件,$file_time){$spreadsheet new Spreadsheet();$sheet $spreadsheet->getActiveSheet();foreach ($list as $key >…

【靶机】vulnhub靶机clover:1

靶机下载地址&#xff1a; Clover: 1 ~ VulnHub 靶机ip&#xff1a;192.168.174.145 Kali ip&#xff1a;192.168.174.128 靶机ip发现&#xff1a;sudo arp-scan -l 靶机开放端口扫描 分析&#xff1a; 发现开放了21端口ftp服务&#xff0c;且允许匿名登录 22端口ssh服务 8…

【交换机转发工作原理【泛洪、转发、丢弃】】

新的一年 新的征程 新的课程开班 等你来学&#xff01; ​一、交换机的三张表 在讲交换机转发工作原理前&#xff0c;先介绍交换机的三张表&#xff1a; 1.ARP缓存表&#xff1a;ARP(Address Resolution Protocol)地址解析协议&#xff0c;主要作用将目的IP地址解析(映射…

Linux系统中GDB调试详细操作方法

第一&#xff1a;启动 在shell下敲gdb命令即可启动gdb&#xff0c;启动后会显示下述信息&#xff0c;出现gdb提示符。 添加编译指令&#xff1a;gcc -g a.c -o test 打开指令&#xff1a;gdb test 或者 gdb 然后输入&#xff1a;file test ➜ example gdb …

数据结构:简单排序方法(插入排序和起泡排序)

1、插入排序 插入排序(insertion sort)的基本操作是将当前无序序列区 R[i…n]中的记录 R[i]“插入”到有序序列区 R[1…i-1]中,使有序序列区的长度增 1,如图所示。 例如,对下列一组记录的关键字: (49,38,65,76,27,13 ,91,52) &#xff08;3-4&#xff09; 进行插人排序的过…

【c语言】数据结构-带头双向循环链表

主页&#xff1a;114514的代码大冒险 qq:2188956112&#xff08;欢迎小伙伴呀hi✿(。◕ᴗ◕。)✿ &#xff09; Gitee&#xff1a;庄嘉豪 (zhuang-jiahaoxxx) - Gitee.com 目录 共八种链表&#xff1a; 前言 一、结构简图与分析 二、链表操作详解&#xff08;附代码实现&am…

js:原生ajax【纯js】

同步与异步区别同步&#xff1a;按顺序&#xff0c;依次执行&#xff0c;向服务器发送请求-->客户端做其他操作异步&#xff1a;分别执行&#xff0c;向服务器发送请求>同时执行其他操作原生ajax创建ajax对象var anew xmlhttprequest();设定数据的传输方式&#xff08;ge…