【算法与数据结构】198、213、337LeetCode打家劫舍I, II, III

文章目录

  • 一、198、打家劫舍
  • 二、213、打家劫舍 II
  • 三、337、打家劫舍III
  • 三、完整代码

所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。

一、198、打家劫舍

在这里插入图片描述
  思路分析:打家劫舍是动态规划的的经典题目。本题的难点在于递归公式和初始化。

  • 第一步, d p [ j ] dp[j] dp[j]的含义。 d p [ j ] dp[j] dp[j]代表到第 j j j家的时候,偷窃到的最高金额。
  • 第二步,递推公式。 d p [ j ] dp[j] dp[j]仅仅与 d p [ j − 1 ] dp[j-1] dp[j1] d p [ j − 2 ] dp[j-2] dp[j2]有关。如果不偷第 j j j家,则偷窃金额不变, d p [ j ] = d p [ j − 1 ] dp[j] = dp[j-1] dp[j]=dp[j1]。如果偷第 j j j家,那么偷窃金额在 d p [ j − 2 ] dp[j-2] dp[j2]基础上加上 n u m s [ i ] nums[i] nums[i],即 d p [ j ] = d p [ j − 2 ] + n u m s [ i ] dp[j] = dp[j-2] + nums[i] dp[j]=dp[j2]+nums[i]。综合二者, d p [ j ] = m a x ( d p [ j − 1 ] , d p [ j − 2 ] + n u m s [ i ] ) dp[j] = max(dp[j-1], dp[j-2] + nums[i]) dp[j]=max(dp[j1],dp[j2]+nums[i])
  • 第三部,元素初始化。 d p [ 0 ] dp[0] dp[0]初始化为0,代表还没开始偷窃; d p [ 1 ] dp[1] dp[1]初始化为 n u m [ 0 ] num[0] num[0]
  • 第四部,递归顺序。循环从 j = 2 j = 2 j=2开始。
  • 第五步,打印结果。
      程序如下
// 198、打家劫舍,动态规划
class Solution {
public:int rob(vector<int>& nums) {vector<int> dp(nums.size() + 1, 0);dp[1] = nums[0];for (int i = 2; i <= nums.size(); i++) {dp[i] = max(dp[i - 1], dp[i - 2] + nums[i-1]);}return dp[nums.size()];}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

  因为只用到了dp数组的最后一个元素,实际上不需要保存所有的元素。因此对上述代码进行内存优化,将空间复杂度降低到 O ( 1 ) O(1) O(1),但是递归的过程不明显,找bug费劲。

// 198、打家劫舍,动态规划-内存优化
class Solution2 {
public:int rob(vector<int>& nums) {if (nums.size() == 0) return 0;if (nums.size() == 1) return nums[0];int first = nums[0], second = max(nums[0], nums[1]);for (int i = 2; i < nums.size(); i++) {int temp = second;second = max(second, first + nums[i]);first = temp;}return second;}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

二、213、打家劫舍 II

在这里插入图片描述

  思路分析:本题是打家劫舍I的升级版,要求第一家和最后一家是连着的,不能同时偷。这是一个非此即彼的问题。要么偷第一家,不偷最后一家,这等于将最后一家排除在外。反之,不偷第一家,偷最后一家,等价于将第一家排除在外。假设第一家的下标为 0 0 0,最后一家的下标为 i − 1 i-1 i1,那么一共有两种情况:偷窃范围 [ 0 , i − 2 ] [0, i - 2] [0,i2],偷窃范围 [ 1 , i − 1 ] [1, i - 1] [1,i1]。然后应用打家劫舍I的思路来做即可。以下是动态规划的代码,内存优化版本就没给出了,思路都是一样的。

  程序如下

// 213、打家劫舍II,动态规划
class Solution3 {
public: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);}int robRange(vector<int>& nums, int start, int end) {if (end == start) return nums[start];vector<int> dp(nums.size(), 0);dp[start] = nums[start];dp[start + 1] = max(nums[start], nums[start + 1]);for (int i = start + 2; i <= end; i++) {dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);}return dp[end];}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

三、337、打家劫舍III

在这里插入图片描述
在这里插入图片描述

  思路分析:本题是打家劫舍I的变体,原题目中的数组变成了二叉树。本题涉及到树形递归和动态规划,我们就结合递归三部曲和动态规划五步骤:

  • 1、返回值和递归参数。我们需要判断一个节点要不要偷,而偷不偷取决于动作带来的收益。因此,我们需要返回一个节点偷与不偷的两个状态所得的金额。这就是一个长度为2的数组。这里我们假设这个二维数组第一个元素代表不偷的收益,第二个元素代表偷的收益,{ 0 , 1 = 不偷的收益,偷的收益 {0, 1} = {不偷的收益,偷的收益} 0,1=不偷的收益,偷的收益}。输入参数是当前节点。
  • 2、确定终止条件。当遇到空节点就返回,空节点不会带来收益。因此返{ 0 , 0 {0,0} 0,0}。
	if (cur == NULL) return vector<int>{0, 0};
  • 3、确定遍历顺序。因为当前节点偷不偷需要根据左右孩子的返回值来进行判断,所以 我们需要先得到左右孩子的返回值,即先遍历左右孩子。在所有的遍历顺序中,只有后序遍历(左右中遍历顺序)满足。
	vector<int> left = robTree(cur->left); // 左vector<int> right = robTree(cur->right); // 右
  • 4、确定单层递归逻辑。对于当前节点来说,只有两个情况。如果偷当前节点,那么左右孩子节点就不能偷,偷的收益=左孩子不偷的收益+右孩子不偷的收益。如果不偷当前节点,那么左右孩子节点可偷可不偷,至于究竟偷不偷就看那个收益大(注意偷的收益未必更大,偷了小的金额,旁边大的金额就偷不了)。不偷的收益 = max(左孩子不偷的收益,左孩子偷的收益)+max(右孩子不偷的收益,右孩子偷的收益)。将文字抽象成公式:
 	int val1 = cur->val + left[0] + right[0];   // 偷当前节点,那么左右孩子节点不能偷int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不偷当前节点,那么左右孩子节点可以偷也可以不偷,取决于偷或者是不偷的金额。
  • 5、具体示例推导dp数组,验证。
      程序如下
// 337、打家劫舍III动态规划
class Solution {
public:int rob(TreeNode* root) {vector<int> result = robTree(root);return max(result[0], result[1]);}vector<int> robTree(TreeNode* cur) {    // 返回一个二维数组, {0, 1} = {不偷的金额,偷的金额}if (cur == NULL) return vector<int>{0, 0};vector<int> left = robTree(cur->left);vector<int> right = robTree(cur->right);int val1 = cur->val + left[0] + right[0];   // 偷当前节点,那么左右孩子节点不能偷int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不偷当前节点,那么左右孩子节点可以偷也可以不偷,取决于偷或者是不偷的金额。return { val2, val1 };}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),每个节点只遍历了一次。
  • 空间复杂度: O ( l o g n ) O(log n) O(logn),算上递推系统栈的空间。

三、完整代码

// 打家劫舍I, II
# include <iostream>
# include <vector>
# include <algorithm>
using namespace std;// 198、打家劫舍,动态规划
class Solution {
public:int rob(vector<int>& nums) {vector<int> dp(nums.size() + 1, 0);dp[1] = nums[0];for (int i = 2; i <= nums.size(); i++) {dp[i] = max(dp[i - 1], dp[i - 2] + nums[i-1]);}return dp[nums.size()];}
};// 198、打家劫舍,动态规划-内存优化
class Solution2 {
public:int rob(vector<int>& nums) {if (nums.size() == 0) return 0;if (nums.size() == 1) return nums[0];int first = nums[0], second = max(nums[0], nums[1]);for (int i = 2; i < nums.size(); i++) {int temp = second;second = max(second, first + nums[i]);first = temp;}return second;}
};// 213、打家劫舍II,动态规划
class Solution3 {
public: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);}int robRange(vector<int>& nums, int start, int end) {if (end == start) return nums[start];vector<int> dp(nums.size(), 0);dp[start] = nums[start];dp[start + 1] = max(nums[start], nums[start + 1]);for (int i = start + 2; i <= end; i++) {dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);}return dp[end];}
};int main() {vector<int> nums = { 1,2,3,1 };Solution3 s1;int result = s1.rob(nums);cout << result << endl;system("pause");return 0;
}
// 337、打家劫舍III
# include <iostream>
# include <vector>
# include <string>
# include <queue>
using namespace std;// 树节点定义
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) {}
};template<typename T>
void my_print(T& v, const string msg)
{cout << msg << endl;for (class T::iterator it = v.begin(); it != v.end(); it++) {cout << *it << ' ';}cout << endl;
}template<class T1, class T2>
void my_print2(T1& v, const string str) {cout << str << endl;for (class T1::iterator vit = v.begin(); vit < v.end(); ++vit) {for (class T2::iterator it = (*vit).begin(); it < (*vit).end(); ++it) {cout << *it << ' ';}cout << endl;}
}// 前序遍历迭代法创建二叉树,每次迭代将容器首元素弹出(弹出代码还可以再优化)
void Tree_Generator(vector<string>& t, TreeNode*& node) {if (!t.size() || t[0] == "NULL") return;    // 退出条件else {node = new TreeNode(stoi(t[0].c_str()));    // 中if (t.size()) {t.assign(t.begin() + 1, t.end());Tree_Generator(t, node->left);              // 左}if (t.size()) {t.assign(t.begin() + 1, t.end());Tree_Generator(t, node->right);             // 右}}
}// 层序遍历
vector<vector<int>> levelOrder(TreeNode* root) {queue<TreeNode*> que;if (root != NULL) que.push(root);vector<vector<int>> result;while (!que.empty()) {int size = que.size();  // size必须固定, que.size()是不断变化的vector<int> vec;for (int i = 0; i < size; ++i) {TreeNode* node = que.front();que.pop();vec.push_back(node->val);if (node->left) que.push(node->left);if (node->right) que.push(node->right);}result.push_back(vec);}return result;
}// 337、打家劫舍III动态规划
class Solution {
public:int rob(TreeNode* root) {vector<int> result = robTree(root);return max(result[0], result[1]);}vector<int> robTree(TreeNode* cur) {    // 返回一个二维数组, {0, 1} = {不偷的金额,偷的金额}if (cur == NULL) return vector<int>{0, 0};vector<int> left = robTree(cur->left);vector<int> right = robTree(cur->right);int val1 = cur->val + left[0] + right[0];   // 偷当前节点,那么左右孩子节点不能偷int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不偷当前节点,那么左右孩子节点可以偷也可以不偷,取决于偷或者是不偷的金额。return { val2, val1 };}
};int main() {vector<string> t = { "3", "2", "NULL", "3", "NULL", "NULL", "3", "NULL", "1", "NULL", "NULL"};   // 前序遍历TreeNode* root = new TreeNode();    // 生成根节点Tree_Generator(t, root);            // 生成树vector<vector<int>> tree = levelOrder(root);    // 层序遍历my_print2<vector<vector<int>>, vector<int>>(tree, "目标树:");  // 打印层序遍历Solution s1;int result = s1.rob(root);cout << "最大金额为:" << result << endl;system("pause");return 0;
}

end

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

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

相关文章

机器学习 | 掌握线性回归的实战技巧

目录 初识线性回归 损失和优化 欠拟合与过拟合 正则化线性模型 模型的保存与加载 初识线性回归 线性回归(Linearregression)是利用回归方程(函数)对一个或多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方式。特点是&#xff1a;有一个自变量的情况称为单…

代码随想录刷题笔记 DAY 18 | 找树左下角的值 No.513 | 路经总和 No.112 | 从中序与后序遍历序列构造二叉树 No.106

Day 18 01. 找树左下角的值&#xff08;No. 513&#xff09; 题目链接 代码随想录题解 1.1 题目 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1 示例 2: 输入…

单片机如何实现延时1ms或者1us

1us //适配主频为120MHz的单片机 void Delay_us(int16_t nus) {int32_t temp; SysTick->LOAD nus*15; //120MHzSysTick->VAL0X00;SysTick->CTRL0X01;do { tempSysTick->CTRL;}while((temp&0x01)&&(!(temp&(1<<16))));SysTick->CTRL0x0…

PyCharm 中使用共享虚拟环境的方法

一般情况下&#xff0c; PyCharm 默认会在项目的虚拟环境中安装所需的依赖包&#xff0c;而每个项目都有自己的虚拟环境。在一个项目中导入了某个包后&#xff0c;这个包只会被安装到该项目的虚拟环境中&#xff0c;其他项目的虚拟环境中并没有这个包&#xff0c;因此导入同样的…

不移其志,踏浪前行 | 北京智和信通召开2023年度工作总结大会

岁聿云暮&#xff0c;新元肇启&#xff0c;2024年1月24日&#xff0c;北京智和信通技术有限公司&#xff08;以下简称“北京智和信通”&#xff09;召开2023年度年终总结大会。会上&#xff0c;各部门负责人全面分析公司业务发展态势&#xff0c;各部门员工依次汇报主要工作情况…

Debezium日常分享系列之:Debezium 2.5.1.Final发布

Debezium日常分享系列之&#xff1a;Debezium 2.5.1.Final发布 一、重大变化1.重新选择列后处理器 二、改进和变化1.MongoDB post-image changes 一、重大变化 1.重新选择列后处理器 构建查询时&#xff0c;重新选择列后处理器使用基于 message.key.columns 的键。对于大多数…

QT 使用 QWebChannel 与 Web 端通信展示文件信息

前言 本文将展示如何使用 QWebChannel 来实现 Web 端与 QT 端之间的交互&#xff0c;同时会通过一个在浏览器端展示文件夹信息的简单例子来展示其具体使用&#xff0c;其功能如下&#xff1a; 获取指定文件夹下的文件信息。通过使用 QT 的 QFileSystemWatcher 对指定文件夹进…

C语言 | 求最大/小值小技巧:fmax、fmin函数

如果你只是因为不想用C语言手写max、min函数&#xff0c;就直接去用iostream中的max、min函数的话&#xff0c;这篇文章可能会有些许帮助。 &#x1f607; fmax、fmin函数用于确定两个指定值的较大/较小值。 头文件 math.h&#xff08;或者cmath&#xff09;。 定义 double …

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷9

某企业根据自身业务需求&#xff0c;实施数字化转型&#xff0c;规划和建设数字化平台&#xff0c;平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”&#xff0c;拟采用开源OpenStack搭建企业内部私有云平台&#xff0c;开源Kubernetes搭建云原生服务平台&#xff0c;选…

Android systemui 编译

目录 简介&#xff1a; 一、步骤 二、下载源码 三、环境配置 四、确定好需要编译版本 五、编译SystemUI 步骤1&#xff1a;进入源代码目录 步骤2&#xff1a;初始化编译环境 步骤3&#xff1a;选择目标设备 步骤4&#xff1a;编译SystemUI 步骤5&#xff1a;查找生成…

网络安全全栈培训笔记(59-服务攻防-中间件安全CVE复现lSApacheTomcataNginx)

第59天 服务攻防-中间件安全&CVE复现&lS&Apache&Tomcata&Nginx 知识点&#xff1a; 中间件及框架列表&#xff1a; lIS,Apache,Nginx,Tomcat,Docker,Weblogic,JBoos,WebSphere,Jenkins, GlassFish,Jira,Struts2,Laravel,Solr,Shiro,Thinkphp,Sprng,Flask,…

Ubuntu系统安装 Redis

环境准备 Ubuntu 系统版本&#xff1a;22.04.3Redis 版本&#xff1a;6.2.12 检查本地 make 环境 make -version若没有安装&#xff0c;则需要安装 sudo apt install make检查本地 gcc 环境 gcc -version若没有安装&#xff0c;则需要安装 sudo apt install gcc。 sudo a…

CodeForces 1858C J - Yet Another Permutation Problem

如何构造才能使其最多 我们只需要将 a , a ∗ 2 a,a*2 a,a∗2,放在一起就能让结果变成最多 我们只需要枚举当前n以内的所有奇数&#xff0c;然后乘上2的次幂次方&#xff0c;就能使的结果不重不漏 由于gcd的特性&#xff0c;d最大值不能超过 n 2 \frac{n}{2} 2n​, 该构造方…

Java8方法引用笔记240128

Java8方法引用笔记240128 方法引用用双冒号:: 当静态方法的参数个数和类型和拉姆达表达式提供的参数相同时, 可以用方法引用 方法引用的类型主要有以下四种&#xff1a; 静态方法引用&#xff1a;类名::staticMethodName 实例方法引用&#xff1a;instanceReference::insta…

服装行业ERP系统解决方案

我国的服装企业大多属于劳动密集型&#xff0c;主要有三种类型&#xff1a;自有品牌服装生产销售企业、接订单生产型企业及处于产业链下游的零售分销企业。在经营过程中&#xff0c;服装行业面临诸多挑战&#xff0c;如流行周期短、季节性强&#xff0c;市场变化快&#xff1b;…

空间数据分析入门POI与莫兰指数基础知识笔记

1. 空间分析与POI 1.1. 什么是POI POI是“Polnt of Information”的缩写&#xff0c;中文可以翻译为“信息点”。POI是地图上任何非地理意义的有意义的点&#xff0c;如商店、酒吧、加油站、医院、车站等。这些点通常包括名称、类别、经纬度和地址等基本信息。此外&#xff0…

实用Python定时点击Chrome网页按钮

# conda create -n python39 python3.9# 导入 selenium 和 time 库 from webdriver_manager.chrome import ChromeDriverManager # python -m pip install webdriver-manager # service ChromeService(executable_pathChromeDriverManager().install()) # driver webdriver.C…

Vue学习Element-ui

声明&#xff1a;本文来源于黑马程序员PDF讲义 Ajax 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是 互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2…

防抖,节流

前言 防抖是回城&#xff0c;节流是攻击 防抖和节流是前端开发中常用的函数优化&#xff0c;可以限制函数的执行频率&#xff0c;提升性能和用户体验。在日常开发中可以遇到一些需要对函数进行优化的场景&#xff0c;比如防止表单反复提交。 防抖、节流区别 防抖&#xff1a…

博云科技与中科可控全面合作,探索前沿金融科技新机遇

2024年1月26日&#xff0c;博云科技与中科可控在昆山高新区成功举办合作签约仪式。昆山市委常委、昆山高新区党工委书记孙道寻、中科可控董事长聂华、博云科技董事长花磊等领导出席了本次签约仪式。 中科可控将利用其在先进计算和智造领域的优势&#xff0c;为博云科技提供有关…