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

news2024/11/15 5:52:49

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

代码:

public int rob(int[] nums) {
    // 如果输入数组为空或者长度为0,则没有房子可打劫,返回0
    if (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数组连接成一个环形结构,因此存在三种情况

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

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

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

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

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

代码:

public int rob(int[] nums) {
    // 如果输入数组为空或长度为0,则没有房子可打劫,返回0
    if (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]代表在拿当前节点的情况下所偷取的最大金额

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

代码:

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 中。
      • res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        
      • 在这种情况下,可以选择打劫或不打劫左右子树,从而得到最大金额。
        res[1] = root.val + left[0] + right[0];
        
      • 在这种情况下,打劫当前节点的值,并且左右子树的最大金额必须是它们分别不打劫的情况。
      • 返回 res 数组,包含当前节点的打劫和不打劫情况的最大金额。

今天的学习就到这里

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

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

相关文章

Containerd 介绍

早之前的 Docker Engine 中就有了 containerd&#xff0c;只不过现在是将 containerd 从 Docker Engine 里分离出来&#xff0c;作为一个独立的开源项目&#xff0c;目标是提供一个更加开放、稳定的容器运行基础设施。分离出来的 containerd 将具有更多的功能&#xff0c;涵盖整…

centos7安装Oracle 11g数据库

目录 一、安装前准备1、安装前置工具&#xff08;安装过可以忽略&#xff09;2、更配yum源2.1、备份原有源&#xff1b;2.2、下载阿里云base源和epel源&#xff1b;2.3、清理yum缓存2.4、生成新的缓存2.5、更新系统中所有软件到最新版&#xff08;按需谨慎操作&#xff09; 3 修…

做代理海外仓赚钱?代理仓如何实现盈利?

随着跨境电商与物流的火热&#xff0c;海外仓作为跨境贸易的新基建&#xff0c;也成为了一门生意。具体来说海外仓商业模式是一种通过在跨境贸易中设置离岸仓库&#xff0c;为客户提供包括商品存储、包装、发货、退货和售后服务等一系列跨境电商服务的商业模式。 海外仓的成本主…

跟《经济学人》学英文:2024年08月10日这期 A history-lover’s guide to the market panic over AI

A history-lover’s guide to the market panic over AI Past technologies offer clues to what comes next 原文&#xff1a; Andrew Odlyzko, a professor of mathematics at the University of Minnesota, has a side hustle: he has become one of the world’s foremo…

19523 最长上升子序列长度

### 分析 1. **问题描述**&#xff1a; - 给定一个序列&#xff0c;要求找到最长上升子序列的长度。 - 子序列可以是不连续的&#xff0c;但必须保持顺序。 2. **解决方案**&#xff1a; - 使用动态规划&#xff08;Dynamic Programming, DP&#xff09;来解决这个问…

RCE---无字母数字webshell

<?php if(isset($_GET[code])){$code $_GET[code];if(strlen($code)>35){die("Long.");}if(preg_match("/[A-Za-z0-9_$]/",$code)){die("NO.");}eval($code); }else{highlight_file(__FILE__); } 分析代码&#xff1a;传参不大于35&…

让可视化大屏摆脱面子工程的12个方法

提到可视化大屏&#xff0c;很多老铁就认为这是面子工程&#xff0c;花里胡哨&#xff0c;没啥用处&#xff0c;这固然和认知有关系&#xff0c;那么有没有办法让可视化大屏摆脱这种认知吗&#xff0c;千汇数据工场介绍往日经验&#xff0c;与大家探讨下。 可视化大屏面子工程…

C语言典型例题37

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 例题3.5 按照按照考试成绩的等级输出百分制分数段&#xff0c;A等为85分以上&#xff0c;B等为70~84分&#xff0c;C等为 60~69分&#xff0c;D等在60分以下&#xff0c;成绩的等级从键盘输入 代码&#xff1a; //…

2024最新上门按摩系统源码APP打包教程!

**xhadmin、免费、开源、可商用** 上门按摩这两年很火&#xff0c;某宝、某鱼上盗版系统盛行&#xff0c;大都是留有后门的系统&#xff0c;加密授权&#xff0c;根本二开不了。 近期很多人反馈我们的上门按摩系统APP打包困难&#xff0c;今天我手把手教大家如何打包上门按摩A…

【CanMVK230】CanMV K230 开箱

【CanMVK230】CanMV K230 开箱 CanMV 是什么CanMV K230开发板硬件资源能做什么 开箱&#xff01;配套资料其他学习资料 K230我买到啦~。话不多说&#xff0c;开始分享我的使用过程。欢迎大神指点。 CanMV 是什么 CanMV开源项目由嘉楠科技&#xff08;Canaan&#xff09;官方创建…

【关于CVE-2024-38077 Windows Server 2012和Windows Server 2018安装安全补丁指南】

文章目录 背景问题描述产生原因解决方案解决步骤1. 安装BypassESU工具2. 补丁安装方法一&#xff1a;使用 Windows 更新功能方法二&#xff1a;手动下载补丁并安装 补丁验证方法一&#xff1a;在“控制面板”-“程序”-”程序和功能”-“已安装更新”中检查是否存在 KBS040434 …

<Linux>进程概念-下

文章目录 目录 前言 一、环境变量 1. PATH 2. HOME 3. 其他环境变量 系统调用接口--getenv 4. 命令行参数 4.1 双参数main 4.2 三参数main 5. 设置环境变量 5.1 本地环境变量 5.1.1 内建命令 5.2 固定环境变量 6. 取消环境变量 7. 小总结 二、程序地址空间 1. 空间划分 2. 进…

haproxy负载均衡(twenty-eight day)

官网&#xff1a; https://www.haproxy.com/ 自由及开放源代码软件 HAPrOxy是一个使用C语言编写的自由及开旅酒代码软性&#xff0c;其提供高可用性、负我均衡&#xff0c;以及基于TCP和HTTP的应用程座代理 HAProxy特别适用于那些负载特大的webi些站点通常又需要会话保挂或七层…

单片机中时钟源(Clock Source)和时基源(Timebase Source)和的联系和区别

问题描述 在单片机中&#xff0c;时钟源&#xff08;Clock Source&#xff09;和时基源&#xff08;Timebase Source&#xff09;是两个与时间相关的基本概念&#xff0c;它们在单片机的时钟系统设计中扮演着重要角色。 区别与联系 1.区别 1.1定义 时钟源&#xff1a;是单片机…

【C语言篇】编译和链接以及预处理介绍(上篇)

文章目录 前言翻译环境和运行环境翻译环境编译预处理&#xff08;预编译&#xff09;编译词法分析语法分析语义分析 汇编 链接 运行环境预处理&#xff08;预编译&#xff09;详解预定义符号#define定义常量#define定义宏带有副作用的宏参数宏替换的规则宏和函数的对比 写在最后…

【git】gitee 提交错误,如何回退

文章目录 查看提交记录设定退回到位置提交 查看提交记录 git log git log如下图所示共2次提交记录 最近一次是错误提交&#xff08;笔者提交是在错误的工作路径上传了&#xff09; 设定退回到位置 git reset --hard hash值 git reset --soft 83fcc380d5250599eca********…

rabbit消息队列

一&#xff1a;消息队列简介 1&#xff1a;主流的消息队列 目前主流的几大消息队列有&#xff1a;RabitMQ、ActiveMQ、RocketMQ、Kafka、ZeroMQ等&#xff0c;也有一些小众的比如Beanstalk&#xff0c;当然我们之前学过的Redis也可以实现消息队列的功能。 &#xff08;1&…

Android全面解析之Context机制(一) :初识Android context

什么是Context 回想一下最初学习Android开发的时候&#xff0c;第一用到context是什么时候&#xff1f;如果你跟我一样是通过郭霖的《第一行代码》来入门android&#xff0c;那么一般是Toast。Toast的常规用法是&#xff1a; Toast.makeText(this, "我是toast", To…

详解【网路编程】之Socket套接字编程

谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注 没错&#xff0c;说的就是你&#xff0c;不用再怀疑&#xff01;&#xff01;&#xff01; 希望我的文章内容能对你有帮助&#xff0c;一起努力吧&#xff01;&#xff01;&#xff01; 1、Socket套接字 Socket 是一个…

4G 和 5G 中的单域注册(VoLTE和VoNR适用)VoNR 中的 CSRetry

目录 1. 4G 和 5G 中的单域注册&#xff08;VoLTE和VoNR适用&#xff09; 1.1 主要内容 1.2 什么是 4/5G 网络中的单域注册 1.3 为什么需要单域注册 1.4 单域注册主要参数之&#xff1a;Dual-Registration-5G-Indicator 1.5 单域注册主要参数之&#xff1a;DualRegistrat…