当前位置: 首页 > news >正文

【算法与数据结构】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

相关文章:

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

文章目录 一、198、打家劫舍二、213、打家劫舍 II三、337、打家劫舍III三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、198、打家劫舍 思路分析&#xff1a;打家劫舍是动态规划的的经典题目。本题的难点在于递归公式…...

React、React Router、JSX 简单入门快速上手

React、React Router、JSX 简单入门快速上手 介绍特点 JSX使用js表达式渲染列表样式控制注意事项 入门脚手架创建react项目安装目录介绍入口文件解析 组件解析介绍函数式组件类组件 事件绑定注意点定义使用事件对象事件处理函数接收额外参数 组件状态状态的定义使用 组件通信父…...

从 0 开始搭建 React 框架

webpack 配置 不再赘述&#xff0c;可参考前三个文章&#xff08;wenpack5 基本使用 1 - 3&#xff09; 使用 react 安装 react、react-dom、babel/preset-react yarn add react react-dom babel/preset-react<!DOCTYPE html> <html lang"en"> <h…...

网站地址怎么改成HTTPS?

现在&#xff0c;所有类型的网站都需要通过 HTTPS 协议进行安全连接&#xff0c;而实现这一目标的唯一方法是使用 SSL 证书。如果您不将 HTTP 转换为 HTTPS&#xff0c;浏览器和应用程序会将您网站的连接标记为不安全。 但用户询问如何将我的网站从 HTTP 更改为 HTTPS。在此页…...

Blender教程(基础)-面的细分与删除、挤出选区-07

一、Blender之面的细分 新建一个立方体&#xff0c;在编辑模式下、选中一个面。 在选中的面上单击右键弹出细分选项&#xff0c;选择细分。 在选中细分后、会默认细分1次。修改细分次数在左下角 二、Blender之面的删除 选择中需要操作的面&#xff0c;在英文状态下按X键弹…...

QT自制软键盘 最完美、最简单、支持中文输入(二)

目录 一、前言 二、本自制虚拟键盘特点 三、中文输入原理 四、组合键输入 五、键盘事件模拟 六、界面 七、代码 7.1 frmKeyBoard 头文件代码 7.2 frmKeyBoard 源文件代码 八、使用示例 九、效果 十、结语 一、前言 由于系统自带虚拟键盘不一定好用&#xff0c;也不一…...

SpringCloud_学习笔记_1

SpringCloud01 1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.0.学习目标 了解微服务架构的优缺点 1.1.单体架构 单体架构&#xff…...

容器算法迭代器初识

#include<iostream> using namespace std; #include<vector> //vetor容器存放内置数据类型 void test01() {//创建了一个vector容器&#xff0c;数组 vector<int> v;//向容器中插入数据v.push_back (10);//尾插 v.push_back (20);v.push_back (30);v.push_ba…...

瑞_力扣LeetCode_二叉搜索树相关题

文章目录 说明题目 450. 删除二叉搜索树中的节点题解递归实现 题目 701. 二叉搜索树中的插入操作题解递归实现 题目 700. 二叉搜索树中的搜索题解递归实现 题目 98. 验证二叉搜索树题解中序遍历非递归实现中序遍历递归实现上下限递归 题目 938. 二叉搜索树的范围和题解中序遍历…...

python爬虫爬取网站

流程&#xff1a; 1.指定url(获取网页的内容) 爬虫会向指定的URL发送HTTP请求&#xff0c;获取网页的HTML代码&#xff0c;然后解析HTML代码&#xff0c;提取出需要的信息&#xff0c;如文本、图片、链接等。爬虫请求URL的过程中&#xff0c;还可以设置请求头、请求参数、请求…...

c# Get方式调用WebAPI,WebService等接口

/// <summary> /// 利用WebRequest/WebResponse进行WebService调用的类 /// </summary> public class WebServiceHelper {//<webServices>// <protocols>// <add name"HttpGet"/>// <add name"HttpPost"/>// …...

银行数据仓库体系实践(11)--数据仓库开发管理系统及开发流程

数据仓库管理着整个银行或公司的数据&#xff0c;数据结构复杂&#xff0c;数据量庞大&#xff0c;任何一个数据字段的变化或错误都会引起数据错误&#xff0c;影响数据应用&#xff0c;同时业务的发展也带来系统不断升级&#xff0c;数据需求的不断增加&#xff0c;数据仓库需…...

微信小程序引导用户打开定位授权通用模版

在需要使用位置信息的页面&#xff08;例如 onLoad 或 onShow 生命周期函数&#xff09;中调用 wx.getSetting 方法检查用户是否已经授权地理位置权限&#xff1a; Page({onLoad: function() {wx.getSetting({success: res > {if (res.authSetting[scope.userLocation]) {/…...

JVM篇----第十篇

系列文章目录 文章目录 系列文章目录前言一、JAVA 强引用二、JAVA软引用三、JAVA弱引用四、JAVA虚引用五、分代收集算法前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧…...

DevSecOps 参考模型介绍

目录 一、参考模型概述 1.1 概述 二、参考模型分类 2.1 DevOps 组织型模型 2.1.1 DevOps 关键特性 2.1.1.1 模型特性图 2.1.1.2 特性讲解 2.1.1.2.1 自动化 2.1.1.2.2 多边协作 2.1.1.2.3 持续集成 2.1.1.2.4 配置管理 2.1.2 DevOps 生命周期 2.1.2.1 研发过程划分…...

什么是okhttp?

OkHttp简介&#xff1a; OkHttp 是一个开源的、高效的 HTTP 客户端库&#xff0c;由 Square 公司开发和维护。它为 Android 和 Java 应用程序提供了简单、强大、灵活的 HTTP 请求和响应的处理方式。OkHttp 的设计目标是使网络请求变得更加简单、快速、高效&#xff0c;并且支持…...

R语言基础学习-02 (此语言用途小众 用于数学 生物领域 基因分析)

变量 R 语言的有效的变量名称由字母&#xff0c;数字以及点号 . 或下划线 _ 组成。 变量名称以字母或点开头。 变量名是否正确原因var_name2.正确字符开头&#xff0c;并由字母、数字、下划线和点号组成var_name%错误% 是非法字符2var_name错误不能数字开头 .var_name, var.…...

CTF-WEB的入门真题讲解

EzLogin 第一眼看到这个题目我想着用SQL注入 但是我们先看看具体的情况 我们随便输入admin和密码发现他提升密码不正确 我们查看源代码 发现有二个不一样的第一个是base64 意思I hava no sql 第二个可以看出来是16进制转化为weak通过发现是个弱口令 canyouaccess 如果…...

【C项目】顺序表

简介&#xff1a;本系列博客为C项目系列内容&#xff0c;通过代码来具体实现某个经典简单项目 适宜人群&#xff1a;已大体了解C语法同学 作者留言&#xff1a;本博客相关内容如需转载请注明出处&#xff0c;本人学疏才浅&#xff0c;难免存在些许错误&#xff0c;望留言指正 作…...

【Docker】在Windows下使用Docker Desktop创建nginx容器并访问默认网站

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

嵌入式面试常问问题

以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...