代码随想录刷题笔记 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;}
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【学习】感受野

感受野&#xff08;receptive field&#xff09;是指在神经网络中&#xff0c;某一层输出的特征图上的一个像素点对应输入图像的区域大小。在深度神经网络中&#xff0c;随着网络层数的增加&#xff0c;特征图的感受野也会逐渐增大。这是因为每一层的卷积操作都会扩大感受野。 …

python面向对象的三大特性:封装,继承,多态

1、面向对象有哪些特性 三种&#xff1a;封装性、继承性、多态性 2、Python中的封装 在Python代码中&#xff0c;封装有两层含义&#xff1a; ① 把现实世界中的主体中的属性和方法书写到类的里面的操作即为封装 ② 封装可以为属性和方法添加为私有权限&#xff0c;不能直…

Flask学习(四):路由转换器

默认的路由转换器&#xff1a; string &#xff08;缺省值&#xff09; 接受任何不包含斜杠的文本int接受正整数float接受正浮点数 path类似 string&#xff0c;但可以包含斜杠uuid接受 UUID 字符串 代码示例&#xff1a; app.route(/user/<username>) def show_u…

50、东北大学、阿尔伯塔大学:Hi-GCN从2个层次角度进行图学习,用来诊断脑部疾病[你这和MVS-GCN套娃呢?]

本文由东北大学医学图像智能计算教育部重点实验室&#xff0c;加拿大阿尔伯塔大学于2020年10.24日发表于<Computers in Biology and Medicine> JCR\IF: Q1\7.7 Abstract&#xff1a; 目的:近年来&#xff0c;脑连接网络已被用于神经系统疾病的分类&#xff0c;如自闭症…

备战蓝桥杯Day27 - 省赛真题-2023

题目描述 大佬代码 import os import sysdef find(n):k 0for num in range(12345678,98765433):str1 ["2","0","2","3"]for x in str(num) :if x in str1:if str1[0] x:str1.pop(0)if len(str1) ! 0:k1print(k)print(85959030) 详…

vue防止用户连续点击造成多次提交

中心思想&#xff1a;在第一次提交的结果返回前&#xff0c;将提交按钮禁用。 方法一&#xff1a;给提交按钮加上disabled属性&#xff0c;在请求时先把disabled属性改成true&#xff0c;在结果返回时改成false 方法二&#xff1a;添加loading遮罩层&#xff0c;可以直接使用e…

什么是MVC三层结构

1.MVC&#xff08;三层结构&#xff09; MVC&#xff08;Model-View-Controller&#xff09;是一种常见的软件设计模式&#xff0c;用于将应用程序的逻辑和界面分离成三个不同的组件。每个组件负责特定的任务&#xff0c;从而提高代码的可维护性和可扩展性。 以前的模式。 遇到…

【开源工程】超经典开源项目数字孪生机房~数字机房楼解决方案

飞渡科技数字孪生IDC机房管理平台&#xff0c;综合运用数字孪生、大数据、物联网等技术&#xff0c;对机房楼宇建筑、机房空间、机柜设备等景观进行3D可视化呈现&#xff0c;对接3D机房动环监控系统&#xff0c;辅助管理人员远程掌握机房机柜信息、PUE信息以及安防情况&#xf…

windows使用docker运行TP6使用swoole内置http服务

1&#xff0c;下载docker-Windows客户端 下载地址&#xff1a;https://www.docker.com/products/docker-desktop docker --version #查看docker版本 docker-compose --version #查看docker-compose版本 2&#xff0c;安装环境 使用一键安装包&#xff1a;https://gitee.com/yes…

常青内容与病毒式内容——哪个更适合 SEO?

常青内容是经得起时间考验的内容&#xff0c;而病毒式内容则是利用特定时代潮流的内容。 如果你曾经考虑过为网站添加内容&#xff0c;你可能听说过常青内容和病毒式内容这两个词。这两个词涵盖了网站所需的基本内容类型。 那么&#xff0c;这两者之间有什么区别&#xff1f;…

PCL 高斯投影反算:高斯投影坐标转大地坐标(C++详细过程版)

目录 一、算法原理二、代码实现三、结果展示四、测试数据PCL 高斯投影反算:高斯投影坐标转大地坐标(C++详细过程版)由CSDN点云侠原创。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理

SpringBoot3学习记录(有ssm基础)

目录 一、SpringBoot3 介绍 SpringBoot3 简介 SpringBoot3 快速入门 入门总结 1.为什么依赖不需要写版本 2.Startrer&#xff08;启动器&#xff09;是什么 3.SpringBootApplication 二、SpringBoot3 配置文件 统一配置管理 使用yaml配置文件&#xff08;推荐&#x…