Studying-代码随想录训练营day40| 198.打家劫舍、213.打家劫舍II、337.打家劫舍III

第40天,动态规划part07,动态规划经典题型“打家劫舍”(ง •_•)ง,编程语言:C++

目录

198.打家劫舍 

213.打家劫舍II 

337.打家劫舍III 

总结 


198.打家劫舍 

文档讲解:代码随想录打家劫舍

视频讲解:手撕打家劫舍

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

学习:本题根据题意,对于一个房间来说,我们有偷和不偷两种选择。我们在选择每一间房间偷还是不偷的时候,都是为了得到最大的金钱数。我们从动规五部曲出发进行分析。

1.确定dp数组以及下标的含义:我们可以设置一个一维dp数组,dp[i]表示从0-i个房间,我们能够偷到的最大值。

2.确定递推公式:dp[i]能够由谁得来呢?这取决于我们对i房间是偷还是不偷。如果我们偷i房间,则dp[i]应该是dp[i - 2]+nums[i],意味着隔一个房间不偷加上本房间的财富的价值。如果我们不偷i房间,则dp[i]应该是dp[i - 1],也即不偷这个房间,我们能偷的最大价值就应该是上一个房间能够偷的最大价值。最后我们可以得到递推公式为dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])。

3.初始化dp数组:由递推公式我们可知需要初始化至少两个数,dp[0]和dp[1]。显然dp[0]表示0号房间的价值,dp[0] = nums[0]。dp[1]则不能直接赋为nums[1]因为对于dp[1]来说也可以选择取或者不取nums[1],因此dp[1]应该是max(nums[0], nums[1])。

4.确定遍历顺序,从i = 2开始遍历到最后一个节点。

5.举例dp数组

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:int rob(vector<int>& nums) {if(nums.size() == 1) return nums[0];//1.确定dp数组以及下标的含义, dp[j]表示偷窃0-i个房间能够得到的最高金额vector<int> dp(nums.size(), 0);//2.确定递推公式:dp[i] = max(dp[i-1],dp[i - 2] + nums[j]);//3.初始化dp数组:dp[0] = nums[0];dp[1] = max(nums[0], nums[1]);//4.确定遍历顺序for(int i = 2; i < nums.size(); i++) {dp[i] = max(dp[i-1], dp[i - 2] + nums[i]);}return dp[nums.size() - 1];}
};

213.打家劫舍II 

文档讲解:代码随想录打家劫舍II

视频讲解:手撕打家劫舍II

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

学习:本题与上一题不同之处在于,本题是一个环形数组,头部和尾部是连在一起的,这会导致第一个房间和最后一个房间不能同时取用。

但换言之我们可以分开讨论这两种情况。我们可以先考虑不包含尾元素的情况,也就是仅考虑[0, nums.size() - 2]的这个区间,对于这个区间来说,我们要求得能够得到的最大金额,就和上一题是一样的了,采用的方法也相同。

同理我们可以再考虑不包含头元素的情况,也就是仅考虑[1, nums.size() - 1]这个区间,对于这个区间来说,同样得到最大金额的办法和上一题一样。最后我们再取这两个方法得到的最大值之中的更大的那个即可。

接着我们来考虑,这样会不会导致出现遗漏呢,对于第一种不考虑尾元素的情况,假如得到的最大值的取用中取用了头元素,那即使是考虑了尾元素,我们也不能取;同理对于第二种不考虑头元素的情况,假如得到的最大值的取用中取用了尾元素,那即使考虑了头元素,我们也还是不能取;而第三种情况假如头元素和尾元素都没有取用,那就意味着[1, nums.size() - 2]中我们能够得到最大值,即使加上头元素和尾元素,也无法得到更大的值,因为第一种情况和第二种情况,已经把这种可能性包括了。

因此,我们可以分两次得到两种可能性的值,最后得到最大的。

代码:要十分注意起始遍历点和终止遍历点的位置。

//时间复杂度O(n) 2n
//空间复杂度O(n) 
class Solution {
public:int robrange(vector<int>&nums, int start, int end) {if(start == end) { //nums.size()为2的情况return nums[start];}//1.确定dp数组以及下标含义vector<int> dp(nums.size()); //2.确定递推公式:dp[i] = max(dp[i-1],dp[i - 2] + nums[i]);//3.初始化dp数组dp[start] = nums[start];dp[start + 1] = max(nums[start], nums[start + 1]);//4.确定遍历顺序for(int i = start + 2; i <= end; i++) {dp[i] = max(dp[i-1],dp[i - 2] + nums[i]);}return dp[end];}int rob(vector<int>& nums) {if(nums.size() == 0) return 0;if(nums.size() == 1) return nums[0];int result1 = robrange(nums, 0, nums.size() - 2); //不考虑尾元素int result2 = robrange(nums, 1, nums.size() - 1); //不考虑头元素return max(result1, result2);}
};

337.打家劫舍III 

文档讲解:代码随想录打家劫舍III

视频讲解:手撕打家劫舍III

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

学习: 本题与上两题不同在于,从一个一维数组变成了一颗二叉树,增大了遍历的复杂度,本质还是隔一个搜一个,但需要在动规的过程中加入树的遍历。

我们尝试从动规五部曲进行分析:

1.确定dp数组,我们尝试使用一个map来保存各节点map<Treenode*,int>dp,dp[i].second表示i节点之下能够得到的最大金额。

2.确定递推公式:同样分两种情况,取用当前节点i,dp[i] = i - >val + dp[i->left->left] + dp[i->left->right] + dp[i->right->right] + dp[i->right->left],也就是当前节点的值,加上它的所有孙子的值。如果不取用节点i呢,dp[i] = dp[i->left] + dp[i->right],也就是它的左右孩子的值。

可以发现到这里,在实际使用的时候,我们需要先遍历所有的节点,且确定哪些节点是哪些节点的孙子孩子,这在实际中是很困难的,因此本题的dp数组的设置,与我们常规的dp数组的设置不同,本题的递推公式主要提供了计算的方式。

方法一:暴力搜索

根据递推公式,我们可以采取暴力搜索的方式来得到最大值:

//时间复杂度(n^2)
//空间复杂度O(logn)
class Solution {
public:int rob(TreeNode* root) {if (root == NULL) return 0;if (root->left == NULL && root->right == NULL) return root->val;// 偷父节点int val1 = root->val;if (root->left) val1 += rob(root->left->left) + rob(root->left->right); // 跳过root->left,相当于不考虑左孩子了if (root->right) val1 += rob(root->right->left) + rob(root->right->right); // 跳过root->right,相当于不考虑右孩子了// 不偷父节点int val2 = rob(root->left) + rob(root->right); // 考虑root的左右孩子return max(val1, val2);}
};

方法二:记忆化递推

实际上我们也能够使用上述递推公式的办法,我们可以一边遍历一边存储最大值的方式进行,这样能够在暴力搜索的基础上,降低时间复杂度,减少重复计算:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:unordered_map<TreeNode* , int> umap; // 记录计算过的结果int rob(TreeNode* root) {if (root == NULL) return 0;if (root->left == NULL && root->right == NULL) return root->val;if (umap[root]) return umap[root]; // 如果umap里已经有记录则直接返回// 偷父节点int val1 = root->val;if (root->left) val1 += rob(root->left->left) + rob(root->left->right); // 跳过root->leftif (root->right) val1 += rob(root->right->left) + rob(root->right->right); // 跳过root->right// 不偷父节点int val2 = rob(root->left) + rob(root->right); // 考虑root的左右孩子umap[root] = max(val1, val2); // umap记录一下结果return max(val1, val2);}
};

方法三:动态规划

本题还有个是将动态规划和递归结合的方法,结合递归三部曲和动规五部曲进行。

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

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

vector<int> robTree(TreeNode* cur) {

换言之我们设置了一个dp数组,dp[0]表示不取用该点时的最大金钱数,dp[1]表示取用该点时的最大金钱数。为啥要设置0,1两种可能,也是为后面的遍历做准备。

2.确定终止条件:遍历到空节点返回

if (cur == NULL) return vector<int>{0, 0};

3.确定遍历顺序:采用后序遍历

vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右

4.确定递推公式:

int val1 = cur->val + left[0] + right[0]; //意味着取用该点时,最大值为该点的值加上左右孩子不用的情况下最大的值。
int val2 = max(left[0], left[1]) + max(right[0], right[1]); //意味着不取用该点时,能够得到的最大值

5.举例推导dp数组:

代码:最后总结得到代码

//时间复杂度O(n)
//空间复杂度O(logn)
class Solution {
public://动态规划+递归//1.确定参数及返回值vector<int> traversal(TreeNode* cur) {//2.确定终止条件if(cur == nullptr) return {0,0};//3.确定遍历顺序vector<int> left = traversal(cur -> left);vector<int> right = traversal(cur -> right);//4.确定递推公式int val1 = cur->val + left[0] + right[0]; //意味着取用该点时,最大值为该点的值加上左右孩子不用的情况下最大的值。int val2 = max(left[0], left[1]) + max(right[0], right[1]); //意味着不取用该点时,能够得到的最大值//返回数值return {val2, val1};}int rob(TreeNode* root) {vector<int> dp = traversal(root);return max(dp[0], dp[1]);}
};

总结 

打家劫舍题型I,II,III,逐一增加难度。第三题也是树形DP的入门题目,通过本题也能够了解树形DP就是在树上进行递归公式的推导。相较于此前我们使用的一维dp数组和二维dp数组还是有很大去别的,需要理解并反复练习。同时我们在第三题中还是用到了记忆化搜索的方式,实际上记忆化搜索也是实现动态规划的方式之一,其与正常的动态规划的方法的区别在于:原文链接

记忆化搜索:「自顶向下」的解决问题,采用自然的递归方式编写过程,在过程中会保存每个子问题的解(通常保存在一个数组或哈希表中)来避免重复计算。

  • 优点:代码清晰易懂,可以有效的处理一些复杂的状态转移方程。有些状态转移方程是非常复杂的,使用记忆化搜索可以将复杂的状态转移方程拆分成多个子问题,通过递归调用来解决。

  • 缺点:可能会因为递归深度过大而导致栈溢出问题。

递推:「自底向上」的解决问题,采用循环的方式编写过程,在过程中通过保存每个子问题的解(通常保存在一个数组或哈希表中)来避免重复计算。

  • 优点:避免了深度过大问题,不存在栈溢出问题。计算顺序比较明确,易于实现。

  • 缺点:无法处理一些复杂的状态转移方程。有些状态转移方程非常复杂,如果使用递推方法来计算,就会导致代码实现变得非常困难。

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

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

相关文章

【C++进阶学习】第七弹——AVL树——树形结构存储数据的经典模块

二叉搜索树&#xff1a;【C进阶学习】第五弹——二叉搜索树——二叉树进阶及set和map的铺垫-CSDN博客 目录 一、AVL树的概念 二、AVL树的原理与实现 AVL树的节点 AVL树的插入 AVL树的旋转 AVL树的打印 AVL树的检查 三、实现AVL树的完整代码 四、总结 前言&#xff1a…

JavaScript青少年简明教程:输入输出

JavaScript青少年简明教程&#xff1a;输入输出 JavaScript的输入输出情况相对复杂&#xff0c;因为它依赖于其运行的宿主环境&#xff08;如Web浏览器或Node.js&#xff09;来提供具体的输入输出机制。JavaScript的核心规范&#xff08;ECMAScript&#xff09;本身并不直接提…

C基础day9

一、思维导图 二、课后练习 1> 使用递归实现 求 n 的 k 次方 #include<myhead.h>int Pow(int n,int k) {if(k 0 ) //递归出口{return 1;}else{return n*Pow(n,k-1); //递归主体} }int main(int argc, const char *argv[]) {int n0,k0;printf("请输入n和k:&…

韩国coupang上线的卖家官网是什么?韩国电商有哪些平台?

根据Statista的调查报告&#xff0c;预计2024年电子商务市场收入将达到4.117亿美元。而韩国的电子商务市场是全球最具活力和创新性的市场之一&#xff0c;有数据显示2023年韩国电商市场规模已突破1700亿美元&#xff0c;全球排名第四。 韩国coupang上线的卖家官网是什么&#x…

Linux虚拟机扩展磁盘空间

文章目录 在VM上进行扩展新的磁盘空间进入虚拟机将扩展的磁盘空间分配给对应的分区 VM 下的Linux虚拟机提示磁盘空间不足&#xff0c;需要对其进行磁盘扩容&#xff0c;主要有以下两步&#xff1a; 在VM上进行扩展新的磁盘空间 先关闭虚拟机在VM的虚拟机设置处进行硬盘扩展 …

Redislnsight-v2远程连接redis

redis安装内容添加&#xff1a; Linux 下使用Docker安装redis-CSDN博客 点击添加 添加ip地址&#xff0c;密码&#xff0c;端口号 创建完成 点击查看内容&#xff1a;

Redis的单线程讲解与指令学习

目录 一.Redis的命令 二.数据类型 三.Redis的key的过期策略如何实现&#xff1f; 四.Redis为什么是单线程的 五.String有关的命令 Redis的学习专栏&#xff1a;http://t.csdnimg.cn/a8cvV 一.Redis的命令 两个基本命令 在Redis当中&#xff0c;有两个基本命令&#xff1…

记录些MySQL题集(3)

MySQL 分区技术深入解析 分区的基本概念 MySQL分区 是一种数据库优化的技术&#xff0c;它允许将一个大的表、索引或其子集分割成多个较小的、更易于管理的片段&#xff0c;这些片段称为“分区”。每个分区都可以独立于其他分区进行存储、备份、索引和其他操作。这种技术主要…

Docker初识及使用研究

公司使用docker&#xff0c;小组成员人人都是默默使用&#xff0c;也没讲解培训&#xff0c;真是搞笑。 记录自己独自研究及使用&#xff1a; 1)自己安装->失败-系统弄崩->安装成功 目录 1. Docker安装-初次安装失败2. Docker安装-初次安装成功 1. Docker安装-初次安装失…

微信小程序密码 显示隐藏 真机兼容问题

之前使用type来控制&#xff0c;发现不行&#xff0c;修改为password属性即可 <van-fieldright-icon"{{passwordType password? closed-eye:eye-o}}"model:value"{{ password }}"password"{{passwordType password ? true: false}}"borde…

PostgreSQL 中如何解决因长事务阻塞导致的其他事务等待问题?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 PostgreSQL 中如何解决因长事务阻塞导致的其他事务等待问题&#xff1f;一、了解长事务阻塞的原因&…

结合实体类型信息(2)——基于本体的知识图谱补全深度学习方法

1 引言 1.1 问题 目前KGC和KGE提案的两个主要缺点是:(1)它们没有利用本体信息;(二)对训练时未见的事实和新鲜事物不能预测的。 1.2 解决方案 一种新的知识图嵌入初始化方法。 1.3 结合的信息 知识库中的实体向量表示&#xff0b;编码后的本体信息——>增强 KGC 2基…

思迈特软件2023H2商业智能和分析软件市场份额增长速度跃居第一

近日&#xff0c;全球知名的IT市场研究与咨询公司IDC发布了《中国商业智能和分析软件市场跟踪报告&#xff0c;2023H2》。根据报告显示&#xff0c;思迈特软件在中国商业智能和分析软件市场份额中位列前五&#xff0c;在中国BI厂商中排名TOP2。 尤其值得一提的是&#xff0c;思…

【数据结构】Splay详解

Splay 引入 Splay旋转操作splay操作插入操作查询x排名查询排名为x删除操作查询前驱/后继模板Splay时间复杂度分析 进阶操作截取区间区间加&#xff0c;区间赋值&#xff0c;区间查询&#xff0c;区间最值区间翻转原序列整体插入指定位置插入整体插入末尾区间最大子段和 一些好题…

C++客户端Qt开发——常用控件(按钮类控件)

2.按钮类控件 ①QPushButton 按钮 继承自QAbstractButton&#xff0c;这个类是⼀个抽象类&#xff0c;是其他按钮的父类 属性 说明 text 按钮中的文本 icon 按钮中的图标 iconSize 按钮中图标的尺寸 shortCut 按钮对应的快捷键 autoRepeat 按钮是否会触发&#xff…

AMD software 将两个显示器合并为一个超宽显示器

最近玩游戏的时候&#xff0c;发现了一个骚操作。 可以将两个显示器&#xff08;更多个的自己去试&#xff0c;不知道&#xff09;组合为一个显示器&#xff0c;注意&#xff0c;这里说的不是将两个显示都连接电脑从而使用双屏显示器&#xff0c; 而是 将两个显示器组合为一个…

基于R语言的水文、水环境模型优化技术及快速率定方法与多模型案例

在水利、环境、生态、机械以及航天等领域中&#xff0c;数学模型已经成为一种常用的技术手段。同时&#xff0c;为了提高模型的性能&#xff0c;减小模型误用带来的风险&#xff1b;模型的优化技术也被广泛用于模型的使用过程。模型参数的快速优化技术不但涉及到优化本身而且涉…

微信小游戏 彩色试管 倒水游戏 逻辑 (二)

最近开始研究微信小游戏&#xff0c;有兴趣的 可以关注一下 公众号&#xff0c; 记录一些心路历程和源代码。 定义一个 Water class 1. **定义接口和枚举**&#xff1a; - WaterInfo 接口定义了水的颜色、高度等信息。 - PourAction 枚举定义了水的倒动状态&#xff0c;…

C双指针元素去重

需求 在尾部插⼊、删除元素是⽐较⾼效的&#xff0c;时间复杂度 是 O(1)&#xff0c;但是如果在中间或者开头插⼊、删除元素&#xff0c;就会涉及数据的搬移&#xff0c;时间复杂度为 O(N)&#xff0c;效率较低。 代码 #include <stdio.h>// 相邻元素去重 int remove…

01 电场强度通量 高斯定理

电场强度通量 高斯定理 5-4 电场强度通量 高斯定理一.电场线二.电场强度通量三.高斯定理四高斯定理应用举例典型电场的电场线分布图形正点电荷与负点电荷的电场线一对等量正点电荷的电场线一对等量异号点电荷的电场线一对不等量异号点电荷的电场线带电平行板电容器的电场线 5-4…