【算法】动态规划(三)——打家劫舍系列问题

article/2025/9/12 18:02:43

目录

一、前言

 二、打家劫舍

(1)198. 打家劫舍Ⅰ

• 整体代码:

(2)213. 打家劫舍 II

• 题目分析

• 整体代码:

(3)337. 打家劫舍Ⅲ

• 思路分析

• 整体代码:

三、补充知识——fmax && fmin

Summery💐💐💐


一、前言

经过之前对动态规划的学习,相信大家对解题步骤和题目分析的技能已经有了很大的提升,接下来我们一起学习动态规划的另一个系列问题

打家劫舍:这系列问题总体围绕着相邻不偷的原则,求最后能偷到的最大金额,这类问题首先想到的肯定是用动态规划的方法,接下来我会用两三道题带大家一同体会!✔️

 二、打家劫舍

(1)198. 打家劫舍Ⅰ

leetcode传送➡️https://leetcode.cn/problems/house-robber/

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

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

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

1. dp数组定义

通过读题,很快就能明确dp数组的含义,及偷到第i间房子所得最大金额,最后返回dp[numsSize-1]即可;

2. 递推公式 

对于第 i 间房子,有偷或者不偷两种选择:

选择,那么第 i-1 间房子就不能投,第 i-2 间房子可以偷,此时dp[i] = dp[i-2] + nums[i];

如果选择不偷,那么第 i-1 间房子就可以偷了,此时dp[i] = dp[i-1];

最终比较偷与不偷的最大金额即可

3. 初始化

由上述递推公式可知,结合dp数组的含义,dp[0] = nums[0];dp[1] = max(nums[0], nums[1]);

4. 遍历顺序

这道题不讲究遍历顺序,可从前往后也可从后往前,道理是一样的。

• 整体代码:

#define max(x,y) (x) > (y) ? (x) : (y) int rob(int* nums, int numsSize) {//特殊情况,只有一间房间时:if (numsSize == 1)return nums[0];//初始化int dp[numsSize];memset(dp, 0, sizeof(dp));dp[0] = nums[0];dp[1] = max(nums[0], nums[1]);//递推for (int i = 2; i < numsSize; i++){dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);}return dp[numsSize - 1];
}

(2)213. 打家劫舍 II

leetcode传送➡️https://leetcode.cn/problems/house-robber-ii/

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

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

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

• 题目分析

由题目可知,房子围成一周,意味着第一个房子和最后一个房子肯定是不能一起偷的;

所以我们可以想到将首尾分开讨论,这样既避开了首位同时偷的情况,又避免了重复缺漏;

最后分别用打家劫舍Ⅰ的方法来求出两种偷法的最大金额

 

1. dp数组定义 

同打家劫舍Ⅰ一样,dp数组的含义依然是偷到第i间房子所得最大金额,最后返回含首和含尾两者取的较大值

2. 递推公式 && 初始化 && 遍历顺序

递推公式、初始化 以及遍历顺序与打家劫舍Ⅰ相同,有不理解的同学可以到上一题回顾;

• 整体代码:

#define max(x,y) (x) > (y) ? (x) : (y) 
int rob(int* nums, int numsSize) {//排除特殊情况if (numsSize == 1)return nums[0];if (numsSize == 2)return max(nums[0], nums[1]);//初始化含首dpint dp_h[numsSize];memset(dp_h, 0, sizeof(dp_h));dp_h[0] = nums[0];dp_h[1] = max(nums[0], nums[1]);//遍历dp_hfor (int i = 2; i < numsSize - 1; i++){dp_h[i] = max(dp_h[i - 2] + nums[i], dp_h[i - 1]);}//初始化含尾dpint dp_t[numsSize];memset(dp_t, 0, sizeof(dp_t));dp_t[1] = nums[1];dp_t[2] = max(nums[1], nums[2]);//遍历dp_tfor (int i = 3; i < numsSize; i++){dp_t[i] = max(dp_t[i - 2] + nums[i], dp_t[i - 1]);}return max(dp_h[numsSize - 2], dp_t[numsSize - 1]);
}

(3)337. 打家劫舍Ⅲ

leetcode传送➡️https://leetcode.cn/problems/house-robber-iii/

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

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

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

示例 2:

输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

• 思路分析

 • 读完题我们大概能知道这道题的题设,相邻结点不能偷,但这道题涉及到了二叉树的概念,如果大家对二叉树不是很了解,可以去我之前的文章了解二叉树的基本知识和三种遍历顺序https://blog.csdn.net/Dusong_/article/details/127061544?spm=1001.2014.3001.5502• 这道题我们从二叉树的根结点开始看,如果偷根结点,那么它的两个子结点就不能偷;如果不偷根结点,那么他的左右结点可以偷也可以不偷

• 也就是说,要判断该结点偷不偷,就需要知道它左右子结点偷与不偷所获得的最大钱币,及这时我们需要用后序遍历(遍历顺序)的方法遍历二叉树!

int* left_dp = robTree(cur->left);   //递归左子树,返回值放入dp数组中
int* right_dp = robTree(cur->right);   //递归右子树,返回值放入dp数组中

1. dp数组定义

与上面两道题不同的是,对于一个结点我们需要他偷和不偷两个最大值,而不是取偷与不偷中一个较大值(我认为根本原因是二叉树不是线性表,不能像前两道题一样在for循环里递推)

所以我们需要在每一个结点定义一个dp数组,

dp[0]表示不偷该结点所获最大金额

dp[1]表示偷该结点所获最大金额

因为为一层递归都会在栈区开辟一个空间,所以每层递归栈区里都会保存该结点的dp数组,出函数会销毁,所以最后将dp数组返回到上一结点即可。

2. 递推公式 

由上述可知:

如果不偷根结点,那么他的左右结点可以偷也可以不偷⬇️

dp[0] = fmax(left_dp[0], left_dp[1]) + fmax(right_dp[0], right_dp[1]);

如果偷根结点,那么它的两个子结点就不能偷⬇️

 dp[1] = left_dp[0] + right_dp[0] + cur->val;

• 整体代码:

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     struct TreeNode *left;*     struct TreeNode *right;* };*/
int* robTree(struct TreeNode* cur)
{ //递归到空结点,说明该结点偷与不偷都为0,返回{0,0}即可if (cur == NULL){int* dp = (int*)malloc(sizeof(int) * 2);dp[0] = 0;dp[1] = 0;return dp;}//后序遍历int* left_dp = robTree(cur->left);   //递归左子树int* right_dp = robTree(cur->right);   //递归右子树int* dp = (int*)malloc(sizeof(int) * 2);//不偷该结点dp[0] = fmax(left_dp[0], left_dp[1]) + fmax(right_dp[0], right_dp[1]);//偷该结点dp[1] = left_dp[0] + right_dp[0] + cur->val;return dp;
}int rob(struct TreeNode* root) {int* ret = (int*)malloc(sizeof(int) * 2);ret = robTree(root);return fmax(ret[0], ret[1]);
}

Summery💐💐💐

到这里leetcode上三道打家劫舍的问题就已经解决完了,希望大家有所收获👌

这篇文章制作还是比较粗糙,希望大家见谅,也非常感谢能阅读到这里的同学!🙏

动态规划所涉及的问题广泛且多样,希望我们能共破难关!


http://chatgpt.dhexx.cn/article/ioOX2fDI.shtml

相关文章

动态规划之打家劫舍系列

前言 打家劫舍问题是一种非常经典的有限制条件的动态规划问题&#xff0c;按理说&#xff0c;不是一种特殊的类型&#xff0c;但是因为力扣上纯纯的出了三道题&#xff08;1&#xff0c;2&#xff0c;3&#xff09;来考察&#xff0c;题目的难度是依次递进的&#xff0c;还结合…

动态规划之打家劫舍

动态规划之打家劫舍 文章目录 动态规划之打家劫舍1. "198. 打家劫舍"2. "198. 打家劫舍&#xff08;变种&#xff1a;输出路径&#xff09;"3. "213. 打家劫舍 II"4. "337. 打家劫舍 III" 1. “198. 打家劫舍” dp数组定义&#xff1a…

oracle 根据部分字段去重

问题&#xff1a;在oracle中使用group by分组&#xff0c;group by子句中必须包含所有的select中的字段和order by子句中的字段。 在不使用group by子句的情况下&#xff0c;进行分组。&#xff08;根据部分字段分组&#xff09; over()分析函数 原sql SELECTIM. ID mediaGrou…

oracle字段去重查询,oracle怎么去重查询

oracle去重查询的方法是&#xff1a; oracle 数据库多字段去重 方法介绍&#xff1a;distinct 关键字、group by 、row_number ()over(partition by 列 order by 列 desc) 我的需求是&#xff1a;根据某几列去重 查询出去重后的全部信息。最后我选择的是第三种方法。 我的想法&…

oracle 数据去重方法

1. 创建表&#xff1a; -- Create table create table TEST_USER (user_id NUMBER(3),user_name VARCHAR2(20),user_age NUMBER(3) ) tablespace GUAN_TABLESPACEpctfree 10initrans 1maxtrans 255storage(initial 64Knext 1Mminextents 1maxextents unlimited);--测试数据…

oracle 字符串去重

select regexp_replace(1,1,3,5,5, ([^,])(,\1)*(,|$), \1\3) from dual;注意&#xff1a; 但是&#xff0c;这个去重&#xff0c;必须建立在排序的基础上&#xff0c;如果listagg拼接出来的数值像 a, b, a, c 这时候&#xff0c;该正则就会失效。

MYSQL/ORACLE多字段去重-根据某字段去重

通过百度上的答案多数无效 自己搞了个 使用oracle row_number()函数&#xff0c;给每个同名的加一个序号&#xff0c;最后筛选第n个想同的即可 oracle与mysql不同 1.oracel 多字段distinct(字段名去重) group by去重失效 可以用row_number() over(partition) 给同名列加个序号…

Oracle 数据去重

在Oracle数据库中删除重复数据 一&#xff0c;查询及删除重复记录的SQL语句 Person01表&#xff1a; 1. 查询表中多余的重复数据&#xff0c;根据ID字段来判断是否重复 SELECT * FROM PERSON01 WHERE ID IN (SELECT ID FROM PERSON01 GROUP BY ID HAVING COUNT(ID) > 1)…

Oracle根据多列去重

&#xff08;1&#xff09;distinct 关键词 distinct用于返回唯一不同的值&#xff0c;可作用于单列和多列 但必须将其放在开头&#xff0c;否则会提示错误 而若在其后添加多个变量名&#xff0c;则返回的将是这多个变量名不同时重复的列&#xff0c;因而使用distinct筛选某…

oracle 数据库去重查询

oracle数据库中有如下一张表&#xff0c;包含id,loginid,name,researchtime等字段&#xff0c;其中name字段中的数据有重复&#xff0c;查询数据时要重复数据只取一条&#xff0c;利用row_number ()over(partition by 列 order by 列 desc)方法实现 1:select a.,row_number() o…

oracle去重函数

1、distinct &#xff08;1&#xff09;、常用的distinct select distinct column from table; &#xff08;2&#xff09;、统计去重后数量 select count(distinct column) from table;–查去重后数量 &#xff08;3&#xff09;、distinct必须放在开头 select id, distinct n…

oracle 数据库 去重查询

oracle 数据库多字段去重 方法介绍&#xff1a;distinct 关键字、group by 、row_number ()over(partition by 列 order by 列 desc) 我的需求是&#xff1a;根据某几列去重 查询出去重后的全部信息。最后我选择的是第三种方法。 我的想法&#xff1a;我想找出一种更简单的方…

Oracle实现去重的两种方式总结

业务场景 需要查询某数据&#xff0c;由于需要三张表关联查询&#xff0c;查询结果如下&#xff1a; 原始SQL语句 SELECT D.ORDER_NUM AS "申请单号" ,D.CREATE_TIME ,D.EMP_NAME AS "申请人",(SELECT extractvalue(t1.row_data,/root/row/FI13_wasteNam…

mysql默认密码的查找与修改

注&#xff1a;此方法仅可用于初始安装数据库或学习时使用&#xff0c;在实际生产中会使所有数据库文件删除&#xff0c;故应先提前备份相关重要数据&#xff0c;以免造成不必要的损失&#xff0c;请谨慎使用。 若使用mysqld –initialize初始化mysql数据库&#xff0c;会产生一…

rpm安装mysql后密码_CentOs安装Mysql和配置初始密码

装载自&#xff1a;https://www.cnblogs.com/FlyingPuPu/p/7783735.html 一、Mysql下载安装 使用上传命令上传至/home目录&#xff0c;如&#xff1a;rz命令(yum install -y lrzsz) 添加mysql仓库(-Uvh后面接的为你下载的rpm文件名) sudo rpm -Uvh mysql57-community-release-e…

MySQL初始密码的查看

问题&#xff1a;在安装MySQL过程中&#xff0c;以管理员身份运行cmd后进入MySQL的bin目录&#xff0c;然后输入命令“mysqld --initialize”后没有显示初始密码&#xff0c;没办法进行后续的登录怎么办&#xff1f; 1.打开你的MySQL的安装目录下的data文件夹&#xff08;就是…

如何找到mysql的初始密码_如何查看mysql的初始密码

如何查看mysql的初始密码 发布时间:2020-08-26 11:50:11 来源:亿速云 阅读:95 作者:Leah 今天就跟大家聊聊有关如何查看mysql的初始密码,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。 查看mysql的初始密码的…

[mysql]linux服务器mysql默认密码查看

通过 cat /var/log/mysqld.log | grep password 命令查看数据库的密码

centos查看mysql默认密码和修改密码

1、查看mysql默认密码&#xff1a; grep ‘temporary password’ /var/log/mysqld.log rootlocalhost: b_1sZou9FZrt b_1sZou9FZrt就是 2、修改mysql密码&#xff1a; ALTER USER ‘root’‘localhost’ IDENTIFIED BY ‘new password’; ‘new password’替换成你要设置的密…

宝塔中查看mysql默认密码

文章目录 一、查看root密码二、说明三、如何用工具连接数据库 一、查看root密码 二、说明 创建数据库后&#xff0c;请设置一个新的用户&#xff0c;授予操作该库所需的权限&#xff0c;并使用该用户进行数据库操作&#xff0c;不要将root 账户密码设置为 root 123456 admin 等…