C++ day48 打家劫舍

news2025/1/23 11:27:14

题目1:198 打家劫舍

题目链接:打家劫舍

对题目的理解

专业小偷偷盗房屋的钱财,每个房屋存放的金额用非负整数数组表示;

如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警;

不触动警报装置的情况下,一夜之内能偷窃的最高金额;

当前房屋偷与不偷取决于前一个房屋和前两个房屋是否被偷了。即当前状态和前面状态会有一种依赖关系,那么这种依赖关系都是动规的递推公式,打家劫舍是dp解决的经典问题

动规五部曲

1)dp数组及下标i的含义

dp[i]:考虑下标i(包含下标i)之前的房间所能偷的最大的金币为dp[i]

最终结果:dp[nums[i]-1]

2)递推公式

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

偷房间i的金币:dp[i]=dp[i-2]+nums[i],第i-1个房一定是不考虑的,找出下标i-2(包括i-2)以内的房屋

不偷房间i的金币:dp[i]=dp[i-1],考虑i-1房,(注意这里是考虑,并不是一定要偷i-1房)

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

3)dp数组初始化

根据递推公式看出,dp[0]和dp[1]是递推公式的基础,所以初始化为dp[0]=nums[0],dp[1]=max(nums[0],nums[1]),其余非0下标,初i始成任意值均可,因为dp[i]是从前面两个得到的,最终会被计算的结果覆盖

4)遍历顺序

i要从小到大百遍历,这样才能使用前面的值推导后面的dp[i]

for(i=2;i<nums.size();i++)

5)打印dp数组

代码

class Solution {
public:
    int rob(vector<int>& nums) {
        //定义并初始化dp数组
        vector<int> dp(nums.size(),0);
        if(nums.size()==0) return 0;
        if(nums.size()==1) return nums[0];
        dp[0]=nums[0];
        dp[1]=max(nums[0],nums[1]);
        //递推
        for(int i=2;i<nums.size();i++){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[nums.size()-1];   
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

题目2:213 打家劫舍Ⅱ

题目链接:打家劫舍Ⅱ

对题目的理解

房屋围成一圈,最后一个房屋和第一个房屋时紧挨着的,如果两间相邻的房屋在同一晚上被闯入,系统会自动报警,在不触动报警装置的情况下,偷窃到的最高金额

将环形问题转换为线性问题,因为最后一个房间和第一个房间是挨着的,所以进行分类讨论,考虑3种情况,!!!用的是"考虑",例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素!

动规五部曲

1)dp数组及下标初始化

dp[i]:考虑下标i(包含下标i)之前的房屋所能盗窃的最大金币数

2)递推公式

与打家劫舍Ⅰ的递推公式相同dp[i]=max(dp[i-1],dp[i-2]+nums[i]),只不过最终比较考虑首端元素的情况与考虑尾端元素的情况的最大值

3)dp数组初始化

与打家劫舍Ⅰ的初始化相同,dp[0]=nums[0],dp[1]=max(nums[0],nums[1])

4)遍历顺序

从小到大遍历

5)打印dp数组

将打家劫舍Ⅰ的代码封装成函数,然后比较首端和尾端取值的最大值

代码

class Solution {
public:
    int robrange(vector<int>nums,int begin,int end){
        if(end==begin) return nums[begin];
        //定义并初始化dp数组
        vector<int> dp(nums.size(),0);
        dp[begin]=nums[begin];
        dp[begin+1]=max(nums[begin],nums[begin+1]);
        //递推
        for(int i=begin+2;i<=end;i++){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        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);
    }
    
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

题目3:337 打家劫舍Ⅲ

题目链接:打家劫舍Ⅲ

对题目的理解

房子是二叉树的形式,两个直接相连的放在在同一晚被打劫,就会报警,返回不报警的最大金额

本题一定是要后序遍历,因为通过递归函数的返回值来做下一步计算

与打家劫舍,打家劫舍II一样,关键是要讨论当前节点抢还是不抢

如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子注意这里说的是“考虑

暴力搜索法

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
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);
        if(root->right) val1+=rob(root->right->left)+rob(root->right->right);
        //不偷父节点
        int val2 = rob(root->left)+rob(root->right);
        return max(val1,val2);
    }
};
  • 时间复杂度:O(n^2),这个时间复杂度不太标准,也不容易准确化,例如越往下的节点重复计算次数就越多
  • 空间复杂度:O(log n),算上递推系统栈的空间

以上代码超时了,这个递归的过程中其实是有重复计算了,计算了root的四个孙子(左右孩子的孩子)为头结点的子树的情况,又计算了root的左右孩子为头结点的子树的情况,计算左右孩子的时候其实又把孙子计算了一遍。

记忆化递推

使用一个map把计算过的结果保存一下,这样如果计算过孙子了,那么计算孩子的时候可以复用孙子节点的结果。

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
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];
        //偷父节点
        int val1=root->val;
        if(root->left) val1+=rob(root->left->left)+rob(root->left->right);
        if(root->right) val1+=rob(root->right->left)+rob(root->right->right);
        //不偷父节点
        int val2 = rob(root->left)+rob(root->right);
        umap[root]=max(val1,val2);
        return max(val1,val2);
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(log n),算上递推系统栈的空间

动态规划

在上面两种方法,其实对一个节点 偷与不偷得到的最大金钱都没有做记录,而是需要实时计算

树形dp的入门题目,因为是在树上进行状态转移,以递归三部曲为框架,其中融合动规五部曲

递归三部曲

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

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

dp数组以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱,使用一个长度为2的数组,记录当前节点偷与不偷所得到的的最大金钱。

在递归的过程中,系统栈会保存每一层递归的参数

2)确定终止条件

在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回,相当于dp数组的初始化

3)确定遍历顺序

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

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

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

4)确定单层递归逻辑

如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0]

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

最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱},注意顺序不能颠倒,因为递归逻辑里面就是偷与不偷,不偷在前,偷在后

5)打印dp数组

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

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int rob(TreeNode* root) {
        //定义并初始化dp数组
        vector<int> dp=robtree(root);
        return max(dp[0],dp[1]);
    }
    vector<int> robtree(TreeNode* cur){
        if(cur==NULL) return vector<int>{0,0};//偷和不偷两个状态
        vector<int> left=robtree(cur->left);//左孩子
        vector<int> right=robtree(cur->right);//右孩子
        //偷cur
        int val1=cur->val+left[0]+right[0];
        //不偷kur
        int val2=max(left[0],left[1])+max(right[0],right[1]);
        return {val2,val1};//注意这里返回的顺序不要弄反,因为每一层递归需要这个顺序的返回值
    }
};
  • 时间复杂度:O(n),每个节点只遍历了一次
  • 空间复杂度:O(log n),算上递推系统栈的空间

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

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

相关文章

吸积效应:为什么接口会越来越臃肿?我们从一个接口说起

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章&#xff0c;主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等&#xff0c;同时欢迎大家加我微信「java_front」一起交流学习 1 从一个接口说起 1.1 初始接口 假设现在有一个创建订单接口&#xff1a; pub…

C语言每日一题(44)删除排序链表中的重复元素 II

力扣 82 删除排序链表中的重复元素 II 题目描述 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5]示…

mac安装解压缩rar后缀文件踩坑

mac默认能够解压缩zip后缀的文件&#xff0c;如果是rar后缀的自己需要下载相关的工具解压 下载地址&#xff1a; https://www.rarlab.com/download.htm mac我是因特尔芯片所以下载 x64 然后解压缩文件进入目录 rar中 将可执行文件 rar、unrar 移动到 /usr/local/bin目录下即…

【高效开发工具系列】jackson入门使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

ASP.NET Core MVC过滤器

1、过滤器分为授权过滤、资源访问过滤、操作方法&#xff08;Action&#xff09;过滤、结果过滤、异常过滤、终结点过滤。上一次咱们没有说异常过滤和终结点过滤&#xff0c;不过老周后面会说的。对这些过滤器&#xff0c;你有印象就行了。 2、所有过滤器接口都有同步版本和异…

可视化开源编辑器Swagger Editor本地部署并实现远程访问管理编辑文档

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 Swagger Editor本地接口文档公网远程访问1. 部署Swagge…

【Python百练——第3练】矩形类及操作

&#x1f490;作者&#xff1a;insist-- &#x1f490;个人主页&#xff1a;insist-- 的个人主页 理想主义的花&#xff0c;最终会盛开在浪漫主义的土壤里&#xff0c;我们的热情永远不会熄灭&#xff0c;在现实平凡中&#xff0c;我们终将上岸&#xff0c;阳光万里 ❤️欢迎点…

一缕青丝寄相思

10年8月16日七夕节男孩向女孩表白,女孩不知道那天是七夕,也没有读懂男孩的爱,女孩在9月22日中秋,向男孩打开了心门,男孩却没有懂女孩的心思.13年后的一封问候邮件,一束女孩的长发和回不去的青春 洒满阳光的午后 转眼间看到你的笑脸 微笑着你对我说 遇上你认识我真好 你说得好莫…

论文解读--Robust lane detection and tracking with Ransac and Kalman filter

使用随机采样一致性和卡尔曼滤波的鲁棒的车道线跟踪 摘要 在之前的一篇论文中&#xff0c;我们描述了一种使用霍夫变换和迭代匹配滤波器的简单的车道检测方法[1]。本文扩展了这项工作&#xff0c;通过结合逆透视映射来创建道路的鸟瞰视图&#xff0c;应用随机样本共识来帮助消…

力扣日记12.3-【二叉树篇】二叉树的所有路径

力扣日记&#xff1a;【二叉树篇】二叉树的所有路径 日期&#xff1a;2023.12.3 参考&#xff1a;代码随想录、力扣 257. 二叉树的所有路径 题目描述 难度&#xff1a;简单 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径…

JAVA代码优化:CommandLineRunner(项目启动之前,预先加载数据)

CommandLineRunner接口是Spring Boot框架中的一个接口&#xff0c;用于在应用程序启动后执行一些特定的代码逻辑。它是一个函数式接口&#xff0c;只包含一个run方法&#xff0c;该方法在应用程序启动后被自动调用。可以帮助我们在应用程序启动后自动执行一些代码逻辑&#xff…

sharding-jdbc实现分库分表

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 &#x1f605;&#x1f605;最近几天的状态有点不对&#xff0c;所以有几天没有更新了。 当我们的数据量比较大…

css实现简单的抽奖动画效果和旋转效果,还有春联效果

使用css的animation和transform和transition可以实现简单的图片放大缩小&#xff0c;旋转&#xff0c;位移的效果&#xff0c;由此可以延伸的动画效果还是挺多的&#xff0c;比如图片慢慢放大&#xff0c;图片慢慢旋转并放大&#xff0c;图片慢慢变化位置等等&#xff0c; 抽奖…

使用pytorch从零开始实现迷你GPT

生成式建模知识回顾: [1] 生成式建模概述 [2] Transformer I&#xff0c;Transformer II [3] 变分自编码器 [4] 生成对抗网络&#xff0c;高级生成对抗网络 I&#xff0c;高级生成对抗网络 II [5] 自回归模型 [6] 归一化流模型 [7] 基于能量的模型 [8] 扩散模型 I, 扩散模型 II…

MathType 7.5.2中文版软件使用期到了怎么办?

MathType 7.5.2中文版作为一款专业的公式编辑器&#xff0c;MathType受到很多人的青睐&#xff0c;它可以将编辑好的公式保存成多种图片格式或透明图片模式&#xff0c;可以很方便的添加或移除符号、表达式等模板&#xff08;只需要简单地用鼠标拖进拖出即可)&#xff0c;也可以…

Verilog 入门(八)(验证)

文章目录 编写测试验证程序波形产生值序列重复模式 测试验证程序实例从文本文件中读取向量实例&#xff1a;时序检测器 测试验证程序用于测试和验证设计方法的正确性。Verilog 提供强有力的结构来说明测试验证程序。 编写测试验证程序 测试验证程序有三个主要目的&#xff1a;…

Java 表达式引擎

企业的需求往往是多样化且复杂的&#xff0c;对接不同企业时会有不同的定制化的业务模型和流程。我们在业务系统中使用表达式引擎&#xff0c;集中配置管理业务规则&#xff0c;并实现实时决策和计算&#xff0c;可以提高系统的灵活性和响应能力。 引入规则引擎似乎就能解决这个…

UE学习C++(1)创建actor

创建新C类 在 虚幻编辑器 中&#xff0c;点击 文件&#xff08;File&#xff09; 下拉菜单&#xff0c;然后选择 新建C类...&#xff08;New C Class...&#xff09; 命令&#xff1a; 此时将显示 选择父类&#xff08;Choose Parent Class&#xff09; 菜单。可以选择要扩展的…

你知道MySQL 中的 order by 是怎么工作的吗?

欢迎大家到我的博客浏览。order by是怎么工作的&#xff1f; | YinKais Blog今天我们来看一下 MySQL 中 “ order by ” 是怎么工作的。 我们以一个实际的例子&#xff0c;来探讨这个问题&#xff1a; 假设我们的表是这样定义的&#xff1a; CREATE TABLE t (id int(11) NOT…

数据结构和算法-树与二叉树的存储结构以及树和二叉树和森林的遍历

文章目录 二叉树的存储结构二叉树的顺序存储二叉树的链式存储小结 二叉树的先中后序遍历例题小结 二叉树的层次遍历小结 由遍历序列构造二叉树一个遍历序列即使给定了前中后序&#xff0c;也不能确定该二叉树的形态可以确定的序列组合前序中序后序中序层序中序 小结若前序&…