代码随想录刷题笔记 Day 52 | 打家劫舍 No.198 | 打家劫舍 II No.213 | 打家劫舍III No.337

文章目录

    • Day 52
      • 01. 打家劫舍(No. 198)
        • <1> 题目
        • <2> 笔记
        • <3> 代码
      • 02. 打家劫舍 II(No. 213)
        • <1> 题目
        • <2> 笔记
        • <3> 代码
      • 03.打家劫舍III(No. 337)
        • <1> 题目
        • <2> 笔记
        • <3> 代码

Day 52

01. 打家劫舍(No. 198)

题目链接

代码随想录题解

<1> 题目

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400
<2> 笔记

打家劫舍问题算是力扣上比较知名的一类动态规划的题目,但其实有了前面背包问题的铺垫,做起来也没有那么难。

题目中讲的是一个小偷去偷沿街的房屋,但是相邻两个房屋不能同时被偷,否则就会触发警报,如何选择打劫的房屋能够使得小偷得到的钱最多。

先来看一下本题的状态,既然题目中问的是偷哪些房屋能获得的钱最多,那状态暂时确定成偷这些房屋能获得的最多的钱是多少。

再来思考一下这个状态能否转移,本题唯一的限制就是相邻的两个房屋不能同时偷,如果要确定现在偷的这个房屋的状态,必须要知道前面的房屋有没有被偷,如果前面的房屋被偷了,那这个状态一定就是上一次的状态(因为不能连着偷嘛)。

所以对于一个房屋应该规定两个状态:

  • 这个房屋被偷的话,最多能获得多少钱。
  • 这个房屋没有被偷的话,最多能获得多少钱。

有了这两个部分就可以推出后面的状态了,如果这个房屋不被偷的话,那前面房屋是否被偷就不会产生影响的,这时候最大值就是前面房屋被偷状态和不被偷状态取一个最大值。

如果这个房屋被偷的话,那一定是建立在前面的房屋没有被偷的状态的基础上,即前面房屋没有被偷的状态加上偷这个房屋的价格。

这两个状态 同时 都要被保存,因为后面的状态需要依靠这两个状态去推出。

确定了一个可以转移的状态,来明确一下 dp 数组的含义,因为要同时存储两个状态,所以 dp 应该被声明为一个二维数组,dp[i][0] 表示这个房屋没有被偷的话,最大值是多少,而
dp[i][1] 表示这个房屋如果被偷的话,最大值是多少。

再来看递推公式,有两个状态也就对应着两个递推公式:

  • dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
  • dp[i][1] = dp[i - 1][0] + nums[i];

如果不好理解的话,可以去回顾一下 dp 数组的含义

dp 数组的初始化:题目中说了 nums.length >= 1 所以可以放心大胆的使用 nums[0] 来作为初始化的条件,当然将数组扩充一下,初始化打劫 0 个房屋的情况也是可以的。

dp[0][0] = 0;
dp[0][1] = nums[0];

现在就可以写出代码了。

<3> 代码
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = dp[i - 1][0] + nums[i];
        }
        return Math.max(dp[n - 1][0], dp[n - 1][1]);
    }
}

02. 打家劫舍 II(No. 213)

题目链接

代码随想录题解

<1> 题目

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入:nums = [1,2,3]
输出:3

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000
<2> 笔记

本题与上一题的区别就是 最后一个和第一个不能同时取得,除此之外就没有任何差异了。

所以大致可以把本题分为三种情况:

  • 第一个和最后一个都不要
    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 要第一个但是不要最后一个
    在这里插入图片描述

  • 要最后一个不要第一个
    在这里插入图片描述

注意这里说的要或者不要不是说一定会去偷第一家或者最后一家,而是要不要考虑偷这两家的情况,所以第一种情况其实是包含在第二种和第三种情况之中的,所以只需要求最后两种情况,然后返回其中的最大值即可。

public int rob(int[] nums) {
    if (nums.length == 1) return nums[0];
    int res1 = getRes(0, nums.length - 2, nums);
    int res2 = getRes(1, nums.length - 1, nums);
    return Math.max(res1, res2);
}

这段代码就是先求得后两种情况的值,然后去取最大值,下面来看一下 getRes 方法的具体实现,为了限制遍历的范围,传入了一个 startIndex 和一个 endIndex,这个下标指的是元素在 nums 中的下标,所以在确认遍历遍历返回的时候,要保证遍历下标 一定要包含 endIndex,如果我们将 dp 数组初始化为这样:dp[endIndex - startInde][2],然后将遍历的范围设置为 [startIndex, dp.length] 会导致计算结果错误的原因,因为这样并不会遍历到 endIndex这个下标。

所以要将 dp 数组的行长度初始化为 dp[endIndex + 1] 然后对 dp[startIndex] 的部分进行初始化,这样就可以保证能够准确规范遍历范围。

int[][] dp = new int[endIndex + 1][2];
dp[startIndex][0] = 0;
dp[startIndex][1] =  nums[startIndex];
<3> 代码
class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1) return nums[0];
        int res1 = getRes(0, nums.length - 2, nums);
        int res2 = getRes(1, nums.length - 1, nums);
        return Math.max(res1, res2);
    }
    public int getRes(int startIndex, int endIndex, int[] nums) {
        int[][] dp = new int[endIndex + 1][2];
        dp[startIndex][0] = 0;
        dp[startIndex][1] =  nums[startIndex];
        for (int i = startIndex + 1; i <= endIndex; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = dp[i - 1][0] + nums[i];
        }
        return Math.max(dp[dp.length - 1][0], dp[dp.length - 1][1]);
    }
}

03.打家劫舍III(No. 337)

题目链接

代码随想录题解

<1> 题目

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额

示例 1:

在这里插入图片描述

输入: root = [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

示例 2:

在这里插入图片描述

输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

提示:

  • 树的节点数在 [1, 104] 范围内
  • 0 <= Node.val <= 104
<2> 笔记

本题是二叉树和动态规划融合的题目;一个节点有两种选择:取或者不取,且只有 父节点 被取的情况下才会影响子节点的状态。

和上面的题目相同,声明一个数组来保存本结点取和不取的情况,因为本题采用的是递归的方式,每一层递归都会存储一个 dp 数组,所以直接将数组声明为一维数组,来存储本层的情况即可。

递推公式和上面相同,但本题牵扯到了二叉树,所以这里要考虑遍历顺序,中序遍历一般二叉搜索树才会用到,暂时不考虑,来考虑用前序遍历还是后序遍历。

  • 前序遍历,就是在进入节点的时候就对节点的值进行处理,所以需要得知上一个节点是否被取得,但是前序遍历存在一个问题:因为本题中如果父节点没有被取的话,那左节点和右节点是可以 同时 被取得的,但是前序遍历在进入左子树和进入右子树之前 没有规范自己到底取没取(因为传入的是dp 包含了两种情况),所以对于以上的情况解决起来比较困难,比如说这样:

    int[] dpLeft = reversal(node.left, lastDP);
    int[] dpRight = reversal(node.right, lastDP);
    

    当递归回到这个节点,也就是第一个递归得到返回值的时候,在本层 无法确定 这个返回值是在取得本层节点还是在没有取得本层节点的情况下退出来的,就无法按照逻辑再对后面的内容进行规范。

  • 后序遍历,而后序遍历就恰好适用于这种需要得到子节点状态的情况,对于一个节点来说,如果要取得本节点,那就是子节点都不取的情况,将 left[0] + right[0] 即可得到结果,如果不取本节点,拿得到的结果就是 Math.max(left[0], left[1]) + Math.max(right[0], right[1])

通过后序遍历就可以保证数组是符合逻辑的;通过本题再次回顾一下后序遍历的使用时机,也就是父节点需要得到子节点状态的时候,所以后序遍历的代码一般是有返回值的。

<3> 代码
class Solution {
    List<Integer> path = new ArrayList<>();
    public int rob(TreeNode root) {
        int[] res = reversal(root);
        return Math.max(res[0], res[1]);
    }
    public int[] reversal(TreeNode node, int[] lastDP) {
        if (node == null) {
            return new int[]{0, 0};
        }
        int[] dp = new int[2];
        int[] dpLeft = reversal(node.left, lastDP);
        int[] dpRight = reversal(node.right, lastDP);
        
        return dp;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【GameFramework框架内置模块】8、文件系统(File System)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a; https://blog.csdn.net/q7…

如何解决网络中IP地址发生冲突故障?

0、前言 本专栏为个人备考软考嵌入式系统设计师的复习笔记&#xff0c;未经本人许可&#xff0c;请勿转载&#xff0c;如发现本笔记内容的错误还望各位不吝赐教&#xff08;笔记内容可能有误怕产生错误引导&#xff09;。 1、个人IP地址冲突解决方案 首先winR&#xff0c;调出…

Centos strema 9 环境部署Glusterfs9

本文档只是创建复制卷&#xff0c;分布式卷&#xff0c;分布式复制卷&#xff0c;纠删卷 操作系统 内核 角色 Ip地址 说明 CentOS Stream 9 x86_64 5.14.0-427.el9.x86_64 客户端 client 192.168.80.119 挂载存储业务机器 CentOS Stream 9 x86_64 5.14.0-427.el9.x8…

Fiddler不仅可以抓包,还可以做接口测试喔

前言 Fiddler最大的优势在于抓包&#xff0c;我们大部分使用的功能也在抓包的功能上&#xff0c;Fiddler做接口测试也是非常方便的。对应没有接口测试文档的时候&#xff0c;可以直接抓完包后&#xff0c;copy请求参数&#xff0c;修改下就可以了。 Composer简介 点开右侧Co…

【推荐系统】NCF神经协同过滤

NCF框架 NCF框架是本文要实现的3个模型的主体结构。 首先是输入层&#xff0c;分别包含两个特征向量 v u v_u vu​和 v i v_i vi​&#xff0c;描述了用户u和物品i。输入仅由一个用户向量和一个物品向量构成&#xff0c;它们分别是以one-hot编码的二值化稀疏向量。 接着是Em…

章节2:单词本该这样记

为什么我们记不住单词&#xff1f; 单词不是被胡编乱造出来的&#xff0c;单词是有规律的&#xff0c;单词是符合人类的逻辑的。 单词实际意思结构意义历史文化 我们要怎么记单词&#xff1f; 掌握单词的结构规律了解与单词有关的历史文化灵活巧计&#xff0c;不要太拘泥于…

MySQL的insert-on-duplicate语句详解

一、insert-on-duplicate语句语法 注意&#xff1a;ON DUPLICATE KEY UPDATE只是 MySQL的特有语法&#xff0c;并不是SQL标准语法&#xff01; INSERT INTO … ON DUPLICATE KEY UPDATE 是 MySQL 中一种用于插入数据并处理重复键冲突的语法。 这个语法适用于在 insert的时候…

MindGraph:文字生成知识图

欢迎来到MindGraph&#xff0c;这是一个概念验证、开源的、以API为先的基于图形的项目&#xff0c;旨在通过自然语言的交互&#xff08;输入和输出&#xff09;来构建和定制CRM解决方案。该原型旨在便于集成和扩展。以下是关于X的公告&#xff0c;提供更多背景信息。开始之前&a…

iOS报错-Command PhaseScriptExecution failed with a nonzero exit code

问题&#xff1a;iOS debug没问题&#xff0c;一打包就报错&#xff1a; Command PhaseScriptExecution failed with a nonzero exit code 解决方法如下&#xff1a; 在项目的Pods目录下&#xff0c;找到Targets Support Files->Pods-xxxx-frameworks.sh 如下&#xff1a…

C语言数组—二维数组

二维数组的创建 //数组创建 int arr[3][4]; //三行四列&#xff0c;存放整型变量 double arr[2][4];二维数组的初始化 我们如果这样初始化&#xff0c;效果是什么样的呢 int arr[3][4] { 1,2,3,4,5,6,7,8,9,10,11,12 };那如果我们不写满十二个呢 int arr[3][4] { 1,2,3,4…

数据可信流通:从运维信任到技术信任

1.数据可信流通概念 "数据可信流通"通常指的是确保数据在不同系统、应用程序或者组织之间的传输和交换过程中的可信性、完整性和安全性。在数据流通的过程中&#xff0c;确保数据的真实性、完整性和保密性是非常重要的&#xff0c;尤其是涉及到敏感信息或者重要数据…

Day21:实现退出功能、开发账号设置、检查登录状态

实现退出功能 将登录凭证修改为失效状态。跳转至网站首页。 数据访问层 不用写了&#xff0c;已经有了updateStatus方法&#xff1b; 业务层 UserService public void logout(String ticket) {loginTicketMapper.updateStatus(ticket, 1);}Controller层 RequestMapping(p…

glib交叉编译

Glib交叉编译 逸一时&#xff0c;误一世。 —— 田所浩二「夏夜银梦」 交叉编译 GLib 涉及到在一个平台上生成能够在另一个平台上运行的目标文件。在这种情况下&#xff0c;我们将会在一台主机&#xff08;通常是开发机器&#xff09;上使用交叉编译工具链来构建 GLib 库&#…

Linux:git的基础操作

git的下载 版本控制系统一般分为两种&#xff0c;集中式版本控制系统&#xff0c;分布式版本控制系统 什么是集中式版本控制系统&#xff1a;版本库集中存放在中央服务器&#xff0c;工作时候使用自己的电脑&#xff0c;当工作时候在中央服务器上拉取最新版本的代码&#xff0c…

微服务:高并发带来的问题的容错方案

1.相关脚本&#xff08;陈天狼&#xff09; 启动nacos客户端&#xff1a; startup.cmd -m standalone 启动sentinel控制台&#xff1a; # 直接使⽤jar命令启动项⽬(控制台本身是⼀个SpringBoot项⽬) java -Dserver.port8080 -Dcsp.sentinel.dashboard.serverlocalhost:808…

【渗透测试实战】用渗透实例sqllibs第46关来讲解SQL报错注入的操作顺序

1、查出库名&#xff08;database()&#xff09; http://127.0.0.1/sqli7/Less-46/?sortupdatexml(1,if(12,1,concat(0x7e,database(),0x7e)),1) 2、通过库名&#xff0c;查表名&#xff08;table_name&#xff09; http://127.0.0.1/sqli7/Less-46/?sortupdatexml(1,if(12…

VMware虚拟机和主机之间无法复制粘贴,移动文件,重新安装vmware-tools变灰,VMware Tools继续运行脚本未能在虚拟机中成功运行。

起初&#xff0c;虚拟机只是无法和主机之间进行复制粘贴&#xff0c;移动文件。查询了很多资料,反反复复地安装卸载vmware-tools&#xff0c;但是都没有成功。通过这篇文章&#xff1a;虚拟机安装VMware Tools的两种方法_vmware tools有3种安装方式-CSDN博客 安装了vmware_too…

中国巨型地下中微子实验室准备探究宇宙奥秘

JUNO设施将于今年上线&#xff0c;将有助于确定哪种类型的中微子质量最高 - 这是物理学中最大的谜团之一。 中国江门地下中微子天文台&#xff08;JUNO&#xff09;的建设工作。朱诺号希望在2024年底之前探测到中微子。图片来源&#xff1a;Qiu Xinsheng/VCG via Getty 开平区…

JAVA八股day1

遇到的问题 相比于包装类型&#xff08;对象类型&#xff09;&#xff0c; 基本数据类型占用的空间往往非常小为什么说是几乎所有对象实例都存在于堆中呢&#xff1f;静态变量和成员变量、成员变量和局部变量的区别为什么浮点数运算的时候会有精度丢失的风险&#xff1f;如何解…

PHP姓名快速匿名化工具(重组脱敏)

PHP姓名重组工具(脱敏/匿名化工具) 将excel数据姓名列粘贴提交&#xff0c;得到随机姓随机中间字随机尾字的重组姓名 那些年自用瞎搞的代码&#xff0c;今日整理成网页交提交得到结果的交互功能分享。 <?php //PHP姓名重组工具(脱敏/匿名化工具) //将excel数据姓名列粘贴…