算法训练营 day51 动态规划 打家劫舍系列

news2025/2/23 18:14:17

算法训练营 day51 动态规划 打家劫舍系列

打家劫舍

198. 打家劫舍 - 力扣(LeetCode)

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

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

  1. 确定dp数组(dp table)以及下标的含义

    dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]

  2. 确定递推公式

    决定dp[i]的因素就是第i房间偷还是不偷。

    如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2] 加上第i房间偷到的钱。

    如果不偷第i房间,那么dp[i] = dp[i - 1],即考 虑i-1房,(注意这里是考虑,并不是一定要偷i-1房,这是很多同学容易混淆的点

    然后dp[i]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);

  3. dp数组如何初始化

    从递推公式dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);可以看出,递推公式的基础就是dp[0] 和 dp[1]

    从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1]);

  4. 定遍历顺序

    dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历!

  5. 举例推导dp数组

    以示例二,输入[2,7,9,3,1]为例。

在这里插入图片描述

红框dp[nums.size() - 1]为结果。

    public static int rob(int[] nums) {
        int[] dp = new int[nums.length];
        if (nums.length<2){
            return nums[0];
        }
        dp[0] = nums[0];
        dp[1] = Math.max(dp[0],nums[1]);
        for (int i = 2;i < nums.length; i++) {
            dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[nums.length-1];
    }

打家劫舍II

213. 打家劫舍 II - 力扣(LeetCode)

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

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

这道题目和198. 打家劫舍 是差不多的,唯一区别就是成环了。

对于一个数组,成环的话主要有如下三种情况:

  • 情况一:考虑不包含首尾元素

在这里插入图片描述

  • 情况二:考虑包含首元素,不包含尾元素

在这里插入图片描述

  • 情况三:考虑包含尾元素,不包含首元素

在这里插入图片描述

注意我这里用的是"考虑",例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素! 对于情况三,取nums[1] 和 nums[3]就是最大的。

而情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以了

分析到这里,本题其实比较简单了。 剩下的和198. 打家劫舍 就是一样的了。

class Solution {
    public  int rob(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];
        return Math.max(robAction(nums,0, nums.length-2),robAction(nums,1, nums.length-1));
    }
    public int robAction(int[] nums, int start, int end) {
        if(start==end) return nums[start];
        int[] dp = new int[nums.length];
        dp[start] = nums[start];
        dp[start+1] = Math.max(nums[start],nums[start+1]);
        for (int i = start+2; i <=end ; i++) {
            dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[end];
    }
}

打家劫舍III

337. 打家劫舍 III - 力扣(LeetCode)

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

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

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

这道题目算是树形dp的入门题目,因为是在树上进行状态转移,我们在讲解二叉树的时候说过递归三部曲,那么下面我以递归三部曲为框架,其中融合动规五部曲的内容来进行讲解

  1. 确定递归函数的参数和返回值

    这里我们要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组。

    dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。

    所以本题dp数组就是一个长度为2的数组!

  2. 确定终止条件

    在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回

  3. 确定遍历顺序

    首先明确的是使用后序遍历。 因为要通过递归函数的返回值来做下一步计算。

    通过递归左节点,得到左节点偷与不偷的金钱。

    通过递归右节点,得到右节点偷与不偷的金钱。

  4. 确定单层递归的逻辑

    如果是偷当前节点,那么左右孩子就不能偷,val1 = cur.val + left[0] + right[0]; (如果对下标含义不理解就再回顾一下dp数组的含义

    如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);

    最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}

  5. 举例推导dp数组

    以示例1为例,dp数组状态如下:(注意用后序遍历的方式推导

在这里插入图片描述

最后头结点就是 取下标0 和 下标1的最大值就是偷得的最大金钱

class Solution {
    public int rob(TreeNode root) {
        int[] res = robTree(root);
        return Math.max(res[0],res[1]);

    }
    public int[] robTree(TreeNode root) {
        int[] res = new int[2];
        if (root==null) return new int[]{0,0};

        int[] left = robTree(root.left);
        int[] right = robTree(root.right);

        res[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
        res[1] = root.val +left[0]+right[0];

        return res;
    }
}

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

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

相关文章

Educational Codeforces Round 143 (Rated for Div. 2) 题解

D. Triangle Coloring 大意&#xff1a; 给定一个有 n 个点 n 条边的无向带权图&#xff0c;保证 n 为 6 的倍数&#xff0c;组成 n/3个三元环&#xff1a; (1,2,3),(4,5,6),⋯。 现在给每个点染上红或蓝两种颜色&#xff0c;要求红色有 n/2 个点、蓝色也有 n/2 个点 。 定义…

【免费教程】 高光谱遥感原理及地表主要信息提取及项目实战经验分享

高光谱分辨率遥感高光谱分辨率遥感是用很窄而连续的光谱通道对地物持续遥感成像的技术。在可见光到短波红外波段其光谱分辨率高达纳米(nm)数量级&#xff0c;通常具有波段多的特点&#xff0c;光谱通道数多达数十甚至数百个以上&#xff0c;而且各光谱通道间往往是连续的&#…

OpenGL ES上下文环境搭建

由于 OpenGL ES 一开始就是为跨平台设计的&#xff0c;所以它本身并不承担窗口管理以及上下文环境构建的职责&#xff0c;这个职责需要由各自的平台来承担。 Android 平台使用的是 EGL&#xff0c;EGL 是 Khronos 创建的一个框架&#xff0c;用来给 OpenGL 的输出与设备的屏幕…

手撸React组件库前必须清楚的9个问题

1. 组件库文档问题 以前常用的组件库文档storybook&#xff0c;包括现在也有用dumi、vitepress做组件库文档等。storybook缺点不美观、webpack的热更新太慢&#xff0c;虽然新版本支持了vite提高了速度但还不算稳定。好在各种文档、mdx、测试等组件第三方工具很多集成进去能很…

day37 动态规划 | 738、单调递增的数字 714、买卖股票的最佳时机含手续费 968、监控二叉树

题目 738、单调递增的数字 给定一个非负整数 N&#xff0c;找出小于或等于 N 的最大的整数&#xff0c;同时这个整数需要满足其各个位数上的数字是单调递增。 &#xff08;当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。&a…

什么是项目沟通管理? 借助系统软件管理项目沟通

在如今竞争激烈的市场环境中&#xff0c;很多企业内部往往有多个项目同时进行着&#xff0c;不同类别的项目需要项目负责人实时跟进&#xff0c;而成功的项目是离不开项目人员之间的日常互相沟通进行。只有良好的沟通&#xff0c;项目经理才能够获取到足够的信息&#xff0c;可…

国产真无线蓝牙耳机哪个好?国产半入耳蓝牙耳机推荐

近几年&#xff0c;生活中随处可见的有戴蓝牙耳机的人&#xff0c;而蓝牙耳机也因为使用更便捷、功能更先进受到了不少用户的喜爱。蓝牙耳机按照佩戴方式来划分&#xff0c;可以有入耳式、半入耳式、头戴式等。在此&#xff0c;我来给大家推荐几款国产半入耳蓝牙耳机&#xff0…

LeetCode 606.根据二叉树创建字符串,102.二叉树的层序遍历和牛客 二叉搜索树与双向链表

文章目录1. 根据二叉树创建字符串2. 二叉树的层序遍历3. 二叉搜索树与双向链表1. 根据二叉树创建字符串 难度 简单 题目链接 解题思路&#xff1a; 这里的意思就是&#xff1a;用前序遍历遍历这颗树。然后左子树和右子树分别在一个括号里。括号里的规则是&#xff1a; 1.左右都…

W800|WIFI|CDK|W80X SDK v1.00.10|官方demo|学习(2):t-connect

W800 SDK代码及相关文档获取地址&#xff1a; https://www.winnermicro.com/html/1/156/158/558.html 1、W800 SDK v1.00.10更新内容&#xff1a; 1. 驱动更新 1&#xff09;提供模组ADC校准功能接口 2&#xff09;修复PSRAM IO复用不完整问题 3&#xff09;Flash驱动修改不再…

CSDN每日一练非降序数组 C语言/C++

题目名称&#xff1a;非降序数组 时间限制&#xff1a;1000ms内存限制&#xff1a;256M 题目描述 写一个函数&#xff0c;传入两个非降序的整数数组&#xff08;A, B&#xff09;&#xff0c;将 A, B 合并成一个非降序数组 C&#xff0c;返回 C&#xff08;不要使用内置 sort 函…

新项目分析

1&#xff1a;数据类型处理 # sep‘\s‘ 这是正则表达式&#xff0c;通过一定规则的表达式来匹配字符串用的 \s 表示空白字符&#xff0c;包括但不限于空格、回车(\r)、换行(\n)、tab或者叫水平制表符(\t)等&#xff0c;这个根据编码格式不同代表的含义也不一样&#xff0c;感…

一文讲解系统调用与函数调用有什么区别?

作为程序员你肯定写过无数的函数&#xff0c;假设有这样两个函数&#xff1a; void funcB() { }void funcA() {funcB(); } 函数之间是可以相互调用的&#xff0c;这很简单很happy有没有。 要知道是代码、是函数就可以相互调用&#xff0c;不管你用什么语言写的。 假设funcB…

2023/02/21 事件循环-eventloop 宏任务 微任务 讲解

1 JS是单线程 js是单线程的。也就是说&#xff0c;同一个时间只能做一件事。作为浏览器脚本语言&#xff0c;与它的用途有关。JavaScript的主要用途是和用户互动&#xff0c;以及操作DOM&#xff0c;这决定了它只能是单线程。 js是单线程的。也就是说&#xff0c;同一个时间只…

如何使用 API 工具做 Websocket 测试

在 API 测试中&#xff0c;对 Websocket 协议的支持呼声越来越高&#xff0c;今天给大家推荐一款 开源的 API 管理工具——Postcat&#xff0c;以及教教大家&#xff0c;如何利用 API 管理工具做 Websocket 测试。 在线 Demo 链接&#xff1a;Postcat - Open Source API Ecosys…

17 个短代码,检验你 Python 基本功

Python 是一门非常优美的语言&#xff0c;其简洁易用令人不得不感概人生苦短。在本文中&#xff0c;蛋糕将带大家回顾 17个非常有用的 Python 技巧&#xff0c;例如查找、分割和合并列表等。这 17 个技巧都非常简单&#xff0c;但它们都很常用且能激发不一样的思路。 人生苦短&…

来一波骚操作,Java内存模型

文章整理自 博学谷狂野架构师 什么是JMM 并发编程领域的关键问题 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息。在编程中&#xff0c;线程之间的通信机制有两种&#xff0c;共享内存和消息传递。 ​ 在共享内存的并发模型里&#xff0c;线程之间共享程序的公共…

项目管理从需求管理开始--不懂需求管理还敢带项目?

分析报告指出&#xff0c;多达76%的项目失败是因为差劲的需求管理&#xff0c;这个是项目失败的最主要原因&#xff0c;比技术、进度失控或者混乱的变更管理还要关键。很多PMO和PM却没有把需求管理重视起来&#xff0c;甚至认为这只是产品经理的事情&#xff0c;自己只做交付即…

Spark RDD及内存计算

文章目录Spark RDD及内存计算性能调优RDD 的核心特征和属性内存计算Spark RDD及内存计算 性能调优 性能调优的本质&#xff1a; 性能调优不是一锤子买卖&#xff0c;补齐一个短板&#xff0c;其他板子可能会成为新的短板。因此&#xff0c;它是 一个动态、持续不断的过程&…

第51篇-某彩网登录参数分析-webpack【2023-02-21】

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析一、前言 今天我们看一个webpack的网站 aHR0cHM6Ly8xMGNhaTUwMC5jYy9sb2dpbg==二、网站分析 首先…

Springboot 全局异常处理类

全局异常处理 在开发过程中&#xff0c;不管是Dao、Servie、Controller,层都有可能发生异常&#xff0c;对于异常处理&#xff0c;通常是try&#xff0d;catch或者直接throw&#xff0c;这会让try&#xff0d;catch的代码在代码中任意出现&#xff0c;系统的代码耦合度高&…