《剑指 Offer》专项突破版 - 面试题 89 和 90 : 房屋偷盗和环形房屋偷盗(C++ 实现)

目录

面试题 89 : 房屋偷盗

面试题 90 : 环形房屋偷盗


 


面试题 89 : 房屋偷盗

题目

输入一个数组表示某条街道上的一排房屋内财产的数目。如果这条街道上相邻的两幢房屋被盗就会自动触发报警系统。请计算小偷在这条街道上最多能偷取到多少财产。例如,街道上 5 幢房屋内的财产用数组 [2, 3, 4, 5, 3] 表示,如果小偷到下标为 0、2 和 4 的房屋内盗窃,那么他能偷取到价值为 9 的财产,这是他在不触发报警系统的情况下能偷取到的最多的财物,如下图所示被盗的房屋上方用特殊符号标出。

分析

小偷一次只能进入一幢房屋内盗窃,因此到街道上所有房屋中盗窃需要多个步骤,每一步到一幢房屋内盗窃。由于这条街道有报警系统,因此他每到一幢房屋前都面临一个选择,考虑是不是能进去偷东西。完成一件事情需要多个步骤,并且每一步都面临多个选择,这看起来是一个适合运用回溯法的问题。但由于这个问题并没有要求列举出小偷所有满足条件的偷盗的方法,而只是求最多能偷取的财物的数量,也就是求问题的最优解,因此这个问题适合运用动态规划。


分析确定状态转移方程

应用动态规划解决问题的关键在于找出状态转移方程。这个问题的输入是一个用数组表示的一排房屋内的财物数量,这个数组就是一个序列。用动态规划解决单序列问题的关键在于找到序列中一个元素对应的解和前面若干元素对应的解的关系,并用状态转移方程表示

假设街道上有 n 幢房屋(分别用 0 ~ n - 1 标号),小偷从标号为 0 的房屋开始偷东西。可以用 f(i) 表示小偷从标号为 0 的房屋开始到标号为 i 的房屋为止最多能偷取到的财物的数量。f(n - 1) 就是小偷从 n 幢房屋中能偷取到的最多财物的数量,即问题的解

小偷在标号为 i 的房屋前有两个选择:

  1. 一个选择是他进去偷东西。由于街道上有报警系统,因此,他不能进入相邻的标号为 i - 1 的房屋内偷东西,之前他最多能偷取到的财物的数量是 f(i - 2)。因此,小偷如果进入标号为 i 的房屋并盗窃,他最多能偷得 f(i - 2) + nums[i](nums 是表示房屋内财物数量的数组)

  2. 另一个选择是小偷进入标号为 i 的房屋,那么他可以进入标号为 i - 1 的房屋内偷东西,因此此时他最多能偷取的财物的数量为 f(i - 1)

那么小偷在到达标号为 i 的房屋时,他能偷取的财物的最大值就是两个选项的最大值,即 f(i) = max(f(i - 2) + nums[i], f(i - 1)),这就是解决这个问题的状态转移方程

上述状态转移方程有一个隐含条件,假设 i 大于或等于 2。当 i 等于 0 时,f(0) = nums[0];当 i 等于 1 时,f(1) = max(nums[0], nums[1])


带缓存的递归代码

状态转移方程是一个递归的表达式,很容易将它转换成递归函数,只是要避免不必要的重复计算。可以创建一个数组 dp,它的第 i 个元素 dp[i] 用来保存 f(i) 的结果。如果 f(i) 之前已经计算出结果,那么只需要从数组 dp 中读取 dp[i] 的值,不再重复计算。如果之前从来没有计算过,则根据状态转移方程递归计算。

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n, -1);
        helper(nums, dp, n - 1);
        return dp[n - 1];
    }
private:
    void helper(vector<int>& nums, vector<int>& dp, int i) {
        if (i == 0)
        {
            dp[i] = nums[0];
        }
        else if (i == 1)
        {
            dp[i] = max(nums[0], nums[1]);
        }
        else if (dp[i] == -1)
        {
            helper(nums, dp, i - 1);
            helper(nums, dp, i - 2);
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
    }
};

函数 helper 其实是将状态转移方程 f(i) = max(f(i - 2) + nums[i], f(i - 1)) 翻译成 C++ 语言的代码。由于状态转移方程要求 i 大于或等于 2,因此函数 helper 还单独处理了 i 分别等于 0 和 1 的这两种特殊情况。

上述代码由于能确保每个 f(i) 只需要计算一次,因此时间复杂度是 O(n)。由于需要一个长度为 n 的数组,因此空间复杂度也是 O(n)。


空间复杂度为 O(n) 的迭代代码

也可以换一种思路,即先求出 f(0) 和 f(1) 的值,然后用 f(0) 和 f(1) 的值求出 f(2),用 f(1) 和 f(2) 的值求出 f(3),以此类推,直至求出 f(n - 1)。这种自下而上的思路通常可以用一个 for 循环实现。

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n);
        dp[0] = nums[0];
        if (n > 1)
            dp[1] = max(nums[0], nums[1]);
        
        for (int i = 2; i < n; ++i)
        {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[n - 1];
    }
};

显然,上述代码的时间复杂度和空间复杂度都是 O(n)。


空间复杂度为 O(1) 的迭代代码

如果仔细观察上述代码,就能发现计算 dp[i] 时只需要用到 dp[i - 1] 和 dp[i - 2] 这两个值,也就是说,只需要缓存两个值就足够了,并不需要一个长度为 n 的数组,因此,可以进一步优化代码的空间效率。

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        int a = nums[0];
        if (n == 1)
            return a;
        
        int b = max(nums[0], nums[1]);
        for (int i = 2; i < n; ++i)
        {
            int c = max(a + nums[i], b);
            a = b;
            b = c;
        }
        return b;
    }
};

优化之后的代码的空间复杂度是 O(1),但时间复杂度仍然是 O(n)。


面试题 90 : 环形房屋偷盗

题目

一条环形街道上有若干房屋。输入一个数组表示该条街道上的房屋内财产的数量。如果这条街道上相邻的两幢房屋被盗就会自动触发报警系统。请计算小偷在这条街道上最多能偷取的财产的数量。例如,街道上 5 家的财产用数组 [2, 3, 4, 5, 3] 表示,如果小偷到下标为 1 和 3 的房屋内盗窃,那么他能偷取到价值为 8 的财产,这是他在不触发报警系统的情况下能偷取到的最多的财物,如下图所示。被盗房屋上方用特殊符号标出。

分析

这个问题和面试题 89 类似,唯一的区别在于面试题 89 中的房屋排成一排,而这个题目中的房屋围成一个环。线形街道上的房屋和环形街道上的房屋存在不同之处。如果 n 幢房屋围成一个首尾相接的环形,那么标号为 0 的房屋和标号为 n - 1 的房屋相邻,如果小偷进入这两幢房屋内都偷东西就会触发报警系统。例如,5 幢房屋内的财产数量分别是 2、3、4、5、3。如果这 5 幢房屋排成一排,小偷如果到标号为 0、2、4 的这 3 幢房屋内偷东西,那么他能偷得价值为 9 的财物。如果这 5 幢房屋围成一个首尾相接的环,由于标号为 0 和 4 的房屋相邻,因此小偷不能同时进入这两幢房屋内偷东西。

由于这个问题和面试题 89 的区别在于小偷不能同时到标号为 0 和 n - 1 的两幢房屋内偷东西。如果他考虑去标号为 0 的房屋,那么他一定不能去标号为 n - 1 的房屋;如果他考虑去标号为 n - 1 的房屋,那么他一定不能去标号为 0 的房屋。因此,可以将这个问题分解成两个子问题:一个问题是求小偷从标号为 0 开始到标号为 n - 2 结束的房屋内能偷得的最多财物数量,另一个问题是求小偷从标号为 1 开始到标号为 n - 1 结束的房屋内能偷得的最多财物数量。小偷从标号为 0 开始到标号为 n - 1 结束的房屋内能偷得的最多财物数量是这两个子问题的解的最大值

可以将面试题 89 的代码稍做修改,这样就可以定义出一个函数使其求出小偷从标号 start 开始到 end 结束的范围内能偷得的最多财物数量,接着分别输入标号从 0 到 n - 2 和从 1 到 n - 1 这两个范围调用该函数就可以解决这个问题。

代码实现

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n == 1)
            return nums[0];
        
        int result1 = helper(nums, 0, n - 2);
        int result2 = helper(nums, 1, n - 1);
        return max(result1, result2);
    }
private:
    int helper(vector<int>& nums, int start, int end) {
        int a = nums[start];
        if (start == end)   
            return a;
        
        int b = max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; ++i)
        {
            int c = max(a + nums[i], b);
            a = b;
            b = c;
        }
        return b;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kafka 3.x(上)

具体课程请看课程简介_哔哩哔哩_bilibili 概念 分布式流处理平台&#xff0c;它以高吞吐量和可扩展性而闻名。相同类型的消息存在于Topic主题中&#xff0c;主题类似于数据库中的表&#xff0c;不过主题存储的数据大多是半结构化的。主题可以包含多个分区&#xff08;分布式的…

STM32之HAL开发——手动移植HAL库

HAL库移植步骤 创建目录 配置启动文件 在\Drivers\CMSIS\Device\ST\stm32f1xx\Source\Templates\ARM目录下&#xff0c;根据你的芯片型号选择对应的启动文件&#xff0c;不同容量大小的芯片&#xff0c;对应的启动文件也不一样。 注意&#xff1a;在HAL库中&#xff0c;不同容…

Git多分支管理实践

想要实现本地文件对远程文件的管理&#xff0c;必须懂得Git的相关操作。 工作中不免会遇到一个仓库多个分支的管理。 git多分支管理属于git的进阶版操作&#xff0c;下面我们来看看。 1. 拉取一个git仓库 git仓库名假设为&#xff1a;test_demo&#xff0c;默认是主仓库&…

企业如何利用数字工厂管理系统打造自动化产线

随着信息技术的飞速发展&#xff0c;数字化转型已成为企业提升生产效率、降低成本、优化管理的重要手段。数字工厂管理系统作为数字化转型的核心组成部分&#xff0c;其在打造自动化产线方面的作用日益凸显。本文将探讨企业如何利用数字工厂管理系统打造自动化产线&#xff0c;…

华为配置WLAN 802.1X认证实验

配置WLAN 802.1X认证示例 组网图形 图1 配置802.1X认证组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤 业务需求 用户接入WLAN网络&#xff0c;使用802.1X客户端进行认证&#xff0c;输入正确的用户名和密码后可以无线上网。且在覆盖区域内移动发生漫游时&…

【前端寻宝之路】学习和总结HTML表格的实现和合并

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-IWDj0gWiFt6IMq3x {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

软件杯 深度学习 机器视觉 人脸识别系统 - opencv python

文章目录 0 前言1 机器学习-人脸识别过程人脸检测人脸对其人脸特征向量化人脸识别 2 深度学习-人脸识别过程人脸检测人脸识别Metric Larning 3 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 人脸识别系统 该项目…

在Linux/Debian/Ubuntu上通过 Azure Data Studio 管理 SQL Server 2019

Microsoft 提供 Azure Data Studio&#xff0c;这是一种可在 Linux、macOS 和 Windows 上运行的跨平台数据库工具。 它提供与 SSMS 类似的功能&#xff0c;包括查询、脚本编写和可视化数据。 要在 Ubuntu 上安装 Azure Data Studio&#xff0c;可以按照以下步骤操作&#xff1…

基于SpringBoot和HeatMap的全球地震热力图可视化实践

目录 前言 一、关于热力图 1、HeatMap简介 2、属性和方法介绍 二、全球地震热力图反演 1、地震信息查询开发 2、前端地图开发 三、地震带反演成果 1、三大地震带反演 2、地震区域分析 总结 前言 众所周知&#xff0c;全球的地震带主要可以分为三处地震带——环太平洋地…

superset 二开增加 flink 数据源连接通过flink sql 查询数据

前言 superset 目前还不支持 flink 的数据源连接&#xff0c;目前我们公司在探索使用数据湖那一套东西&#xff1a; 使用 flink 作为计算引擎使用 paimon oss对象存储对接 flink 作为底层存储使用 superset 通过 flink gateway 查询 paimon 数据形成报表 增加flink数据源 …

Gavin Wood 精彩演讲|安全灵活 JAM 链,打造去中心化多核计算机

Polkadot 年度开发者大会 sub0 Asia 近期在泰国曼谷正式落幕。面对区块链行业的激烈竞争&#xff0c;Polkadot 创始人 Gavin Wood 在演讲中说明将如何利用 Polkadot 2.0 与 JAM 链带来新的技术创新&#xff0c;推动生态持续发展。 Polkadot 将推一个名为 JAM 链的新网络。JAM …

用傅里叶变换和反变换消除噪音信号干扰的软件实例

一、序言 场景一&#xff1a;噪音信号是数据采集处理的天敌&#xff0c;但无时无刻它都存在&#xff0c;于是&#xff0c;信号传输时进行屏蔽防护、模数转换时给予充分的采保时间、电路实现上低通带通处理&#xff0c;为了减小电解电容的感抗作用有时还附加上瓷片电容滤波&…

python的ITS 信息平台的设计与实现flask-django-nodejs-php

第二&#xff0c;陈列说明该系统实现所采用的架构、系统搭建采用的服务器、系统开发环境和使用的工具&#xff0c;以及系统后台采用的数据库。 最后&#xff0c;对系统进行全面测试&#xff0c;主要包括功能测试、查询性能测试、安全性能测试。 分析系统存在的不足以及将来改进…

深度学习pytorch——感知机(Perceptron)(持续更新)

什么是感知机&#xff1f; 感知机是由美国学者FrankRosenblatt在1957年提出来的。感知机是作为神经网络&#xff08;深度学习&#xff09;的起源的算法。因此&#xff0c;学习感知机的构造也就是学习通向神经网络和深度学习的一种重要思想。 感知机接收多个输入信号&#xff0c…

在服务器(Ubuntu20.04)安装用户级别的cuda11.8(以及仿照前面教程安装cuda11.3后安装cudnn和pytorch1.9.0)

1、cuda11.8的下载 首先在cuda官网下载我们需要的cuda版本&#xff0c;这里我下载的是cuda11.8&#xff08;我的最高支持cuda12.0&#xff09; 这里我直接使用wget命令下载不了&#xff0c;于是我直接在浏览器输入后面的链接下载到本地&#xff0c;之后再上传至服务器的&am…

如何使用人工智能和ChatGPT来优化营销转化率

人工智能 &#xff08;AI&#xff09; 和营销的交集正在彻底改变企业与客户互动的方式&#xff0c;最终改变营销转化率。人工智能能够分析大量数据、理解模式和自动执行任务&#xff0c;它不仅是一项创新技术&#xff0c;而且是营销领域的根本性转变。这种转变允许更加个性化、…

Loader和Plugin的区别?编写Loader,Plugin的思路。

一、区别 前面两节我们有提到Loader与Plugin对应的概念&#xff0c;先来回顾下 loader 是文件加载器&#xff0c;能够加载资源文件&#xff0c;并对这些文件进行一些处理&#xff0c;诸如编译、压缩等&#xff0c;最终一起打包到指定的文件中plugin 赋予了 webpack 各种灵活的…

Jupyter服务器端为R语言安装readr包

1.登录debian服务器 方式1.Windows10中可利用putty登录linux服务器 方式2.自从搭建了Jupyter服务器后&#xff0c;还可以从juypyter的终端来登录linux服务器 2.进入R语言命令行 3.安装readr包 >install.packages(‘readr’) …

四川宏博蓬达法律咨询有限公司:法律服务安全的新标杆

在这个法治社会&#xff0c;法律服务行业扮演着越来越重要的角色。四川宏博蓬达法律咨询有限公司&#xff0c;作为行业内的佼佼者&#xff0c;始终坚持以客户为中心&#xff0c;为客户提供专业、高效、安全的法律服务。 一、公司背景与实力展示 四川宏博蓬达法律咨询有限公司自…

python - 更改pdf中文本的字体高亮颜色(fitz模块)

import fitzdoc fitz.open(r"e:/test.pdf") pagedoc[0]# 按照指定的位置设置颜色 highlight page.add_highlight_annot((20, 500,60, 520)) highlight.set_colors(stroke[1, 1, 0]) # light red color (r, g, b) 颜色rgb每个除以255得出 highlight.update()# 按照…