算法日记day 39(动归之打家劫舍)

ops/2024/9/25 2:30:06/

一、打家劫舍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 。

思路:

明确dp数组的含义,本题dp数组的含义为包含前 i 个元素(包含 i)所得的最大值为dp[i],分别有两种情况

第一种

要第 i 个元素:此时第 i-1 个元素不能要,只能从第 i-2个元素拿,表达为dp[i-2]+nums[i]

第二种

不要第 i 个元素:此时应该从第 i-1 个元素选择拿与不拿,表达为dp[i-1]

因此可得递推式为

                                          dp[i] +=max(dp[i-1],dp[i-2]+nums[i])

代码:

java">public int rob(int[] nums) {// 如果输入数组为空或者长度为0,则没有房子可打劫,返回0if (nums == null || nums.length == 0)return 0;// 如果只有一个房子,只能打劫这个房子,直接返回其金额if (nums.length == 1)return nums[0];// dp 数组用来存储到第 i 个房子时能偷到的最大金额int[] dp = new int[nums.length + 1];// 初始化 dp 数组dp[0] = nums[0]; // 偷第一个房子时的最大金额dp[1] = Math.max(nums[0], nums[1]); // 偷第一个或第二个房子中金额较大的一个// 从第三个房子开始计算for (int i = 2; i < nums.length; i++) {// dp[i] 表示偷到第 i 个房子时的最大金额// 选择偷第 i 个房子的情况,应该加上之前能偷到的最大金额(即 dp[i-2]);// 或者选择不偷第 i 个房子,则最大金额等于 dp[i-1]dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);}// 最终结果是到最后一个房子的最大金额return dp[nums.length - 1];
}
  1. 边界条件处理

    • if (nums == null || nums.length == 0) return 0;
      • 处理输入为空或没有房子的情况,直接返回 0
    • if (nums.length == 1) return nums[0];
      • 处理只有一个房子的情况,直接返回那个房子的金额。
  2. 动态规划数组初始化

    • int[] dp = new int[nums.length + 1];
      • 创建一个大小为 nums.length + 1 的数组 dpdp[i] 表示偷到第 i 个房子时的最大金额。
    • dp[0] = nums[0];
      • 如果只有第一个房子,那么偷这个房子的钱就是 dp[0]
    • dp[1] = Math.max(nums[0], nums[1]);
      • 比较前两个房子的金额,取较大的一个,因为只能选择偷第一个房子或第二个房子中的一个。
  3. 状态转移

    • for (int i = 2; i < nums.length; i++)
      • 从第三个房子开始计算。
      • dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        • 选择偷第 i 个房子的情况:此时的金额为 dp[i - 2] + nums[i]
        • 不偷第 i 个房子的情况:此时的金额为 dp[i - 1]
        • 取两者中的最大值作为 dp[i] 的值。
  4. 返回结果

    • return dp[nums.length - 1];
      • 最终返回 dp 数组最后一个元素,即偷到最后一个房子时能获得的最大金额。

二、打家劫舍2 

题目:

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

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

示例 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数组连接成一个环形结构,因此存在三种情况

第一种:选择首元素和中间所有元素,尾元素则不能选取

第二种:选择尾元素和中间所有元素,首元素不能选择

第三种:首位元素均不选择,只选择中间的元素

第三种情况可以包含在第一种情况中,在第一种情况中如果我们不拿首元素即为第三种情况

因此需要讨论第一种和第二种情况中哪个可以拿到更多,取两种情况最大值

代码:

java">public int rob(int[] nums) {// 如果输入数组为空或长度为0,则没有房子可打劫,返回0if (nums == null || nums.length == 0)return 0;int len = nums.length;// 如果只有一个房子,只能打劫这个房子,直接返回其金额if (len == 1)return nums[0];// 计算两种情况的最大值// 1. 从第一个房子到倒数第二个房子(不打劫最后一个房子)// 2. 从第二个房子到最后一个房子(不打劫第一个房子)return Math.max(robAction(nums, 0, len - 1), robAction(nums, 1, len));
}int robAction(int[] nums, int start, int end) {int x = 0; // 代表上一个房子之前的最大金额int y = 0; // 代表上一个房子的最大金额int z = 0; // 当前房子的最大金额// 遍历从 start 到 end(不包括 end)范围内的房子for (int i = start; i < end; i++) {y = z; // 当前的最大金额之前的最大金额z = Math.max(y, x + nums[i]); // 当前房子的最大金额是之前最大金额和加上当前房子金额后的最大值x = y; // 更新 x 为之前的最大金额}return z; // 返回当前房子的最大金额
}

三、打家劫舍3

题目:

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 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

思路:

本题为树形结构,采用后序遍历的方法,相邻节点之间不能同时拿取,在定义dp数组时分别有两种情况,分别是dp[0],代表在不拿当前节点的情况下所偷取的最大金额,dp[1]代表在拿当前节点的情况下所偷取的最大金额

若不拿取当前节点的,则需要在其左右子节点中判断是否要拿去,若拿当前节点,则应跳过其左右子节点去判断是否拿取下一层的金额

代码:

java">public int rob(TreeNode root) {// 调用辅助函数 robAction 计算最大可窃取金额int[] res = robAction(root);// 返回 robAction 的结果中最大的值return Math.max(res[0], res[1]);
}int[] robAction(TreeNode root) {// res[0]: 不打劫当前房子的最大金额// res[1]: 打劫当前房子的最大金额int res[] = new int[2];// 如果当前节点为空,返回 [0, 0]if (root == null)return res;// 递归计算左子树和右子树的结果int[] left = robAction(root.left);int[] right = robAction(root.right);// res[0] 是不打劫当前房子的最大金额// 选择打劫左右子树中最大的金额res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);// res[1] 是打劫当前房子的最大金额// 当前房子的值加上左右子树不打劫的情况res[1] = root.val + left[0] + right[0];// 返回当前节点的结果return res;
}
  1. rob 方法

    • 参数TreeNode root 表示二叉树的根节点。
    • 功能:计算从根节点开始的最大可窃取金额。
    • 步骤
      • 调用 robAction 方法获取当前树的最大可窃取金额。
      • res 数组的两个元素分别表示打劫和不打劫根节点的情况。
      • 使用 Math.max 选择这两种情况中的较大值作为最终结果并返回。
  2. robAction 方法

    • 参数TreeNode root 表示当前处理的节点。
    • 返回值:一个数组 res,其中 res[0] 表示不打劫当前节点时的最大可窃取金额,res[1] 表示打劫当前节点时的最大可窃取金额。
    • 步骤
      • 初始化 res 数组为 [0, 0],表示当前节点为空时的结果。
      • 如果当前节点为 null,直接返回 [0, 0],因为没有任何价值可以打劫。
      • 递归调用 robAction 方法处理左子树和右子树,分别将结果存储在 left 和 right 中。
      • java">res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        
      • 在这种情况下,可以选择打劫或不打劫左右子树,从而得到最大金额。
        java">res[1] = root.val + left[0] + right[0];
        
      • 在这种情况下,打劫当前节点的值,并且左右子树的最大金额必须是它们分别不打劫的情况。
      • 返回 res 数组,包含当前节点的打劫和不打劫情况的最大金额。

今天的学习就到这里


http://www.ppmy.cn/ops/95029.html

相关文章

STM32cubeMX配置Systick的bug

STM32cubeMX版本&#xff1a;6.11.0 现象 STM32cubeMX配置Systick的时钟&#xff0c;不管选择不分频 还是8分频。 生成的代码都是一样的&#xff0c;代码都是不分频。 即不管选择不分频还是8分频&#xff0c;Systick都是使用的系统时钟 函数调用 HAL_Init() → HAL_Init…

html+css+js网页设计 服装设计网站10个页面

htmlcssjs网页设计 服装设计网站10个页面 带js 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 …

ActiveMQ、RabbitMQ、Kafka、RocketMQ在消息回溯、消息堆积+持久化、消息追踪、消息过滤的区别

ActiveMQ、RabbitMQ、Kafka、RocketMQ在消息回溯、消息堆积持久化、消息追踪、消息过滤等方面各有其独特的特点和优势。以下是这四个方面的详细比较&#xff1a; 1. 消息回溯 ActiveMQ&#xff1a;支持消息回溯功能。ActiveMQ可以将消息持久化到磁盘上&#xff0c;因此当需要…

高级java每日一道面试题-2024年8月15日-设计模式篇-工厂模式最主要的好处是什么?在哪里使用?

如果有遗漏,评论区告诉我进行补充 面试官: 工厂模式最主要的好处是什么?在哪里使用? 我回答: 工厂模式&#xff08;Factory Pattern&#xff09;是GoF设计模式中的一种创建型模式&#xff0c;其主要目的是封装创建对象的过程&#xff0c;使创建对象的过程与使用对象的过程…

关于路由和负载均衡

路由 想象你在一个大城市里&#xff0c;想去一个从未去过的新餐馆。你会怎么找到那里&#xff1f;你可能会用手机地图&#xff0c;对吧&#xff1f;地图告诉你从你现在的位置出发&#xff0c;应该先左转&#xff0c;再右转&#xff0c;走哪条街&#xff0c;过几个路口&#xf…

在 C++ 中实现一个简单的图形用户界面(GUI)应用

在 C 中实现一个简单的图形用户界面&#xff08;GUI&#xff09;应用 图形用户界面&#xff08;GUI&#xff09;应用程序是现代软件开发中不可或缺的一部分。它们为用户提供了直观的交互方式&#xff0c;使得操作更加简单和高效。本文将介绍如何在 C 中实现一个简单的 GUI 应用…

Postman文件上传接口测试

接口介绍 返回示例 测试步骤 1.添加一个新请求&#xff0c;修改请求名&#xff0c;填写URL&#xff0c;选择请求方式 2.将剩下的media参数放在请求body里&#xff0c;选择form-data&#xff0c;选择key右边的类型为file类型&#xff0c;就会出现选择文件的按钮Select Files&a…

Linux---DHCP和FTP(原理+实操)

文章目录 DHCP和FTP&#xff08;原理实操&#xff09;DHCP使用DHCP&#xff08;自动分配IP&#xff09;的好处分配方式租约过程第一次重新登录更新租约DHCP服务可分配的地址信息主要包括 DHCP安装和配置实验目的实验环境:网络环境:系统环境:具体操作实操注意一、将三台虚拟机网…