打家劫舍问题 Python题解

news2024/11/25 5:37:10

✅作者简介:人工智能专业本科在读,喜欢计算机与编程,写博客记录自己的学习历程。
🍎个人主页:小嗷犬的个人主页
🍊个人网站:小嗷犬的技术小站
🥭个人信条:为天地立心,为生民立命,为往圣继绝学,为万世开太平。


本文目录

  • 打家劫舍
    • 题目描述
    • 题解
  • 打家劫舍 II
    • 题目描述
    • 题解
  • 打家劫舍 III
    • 题目描述
    • 题解


打家劫舍

题目描述

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

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

示例 1:

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

示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

来源: 198. 打家劫舍 - 力扣 (LeetCode)

题解

动态规划

首先考虑最简单的情况。如果只有一间房屋,则偷窃该房屋,可以偷窃到最高总金额。如果只有两间房屋,则由于两间房屋相邻,不能同时偷窃,只能偷窃其中的一间房屋,因此选择其中金额较高的房屋进行偷窃,可以偷窃到最高总金额。

如果房屋数量大于两间,应该如何计算能够偷窃到的最高总金额呢?对于第 k ( k > 2 ) k (k>2) k(k>2) 间房屋,有两个选项:

  1. 偷窃第 k k k 间房屋,那么就不能偷窃第 k − 1 k−1 k1 间房屋,偷窃总金额为前 k − 2 k−2 k2 间房屋的最高总金额与第 k k k 间房屋的金额之和。
  2. 不偷窃第 k k k 间房屋,偷窃总金额为前 k − 1 k−1 k1 间房屋的最高总金额。

在两个选项中选择偷窃总金额较大的选项,该选项对应的偷窃总金额即为前 k k k 间房屋能偷窃到的最高总金额。

d p [ i ] dp[i] dp[i] 表示前 i i i 间房屋能偷窃到的最高总金额,那么就有如下的状态转移方程:

d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) dp[i] = max(dp[i-2] + nums[i], dp[i-1]) dp[i]=max(dp[i2]+nums[i],dp[i1])

边界条件为:

{ d p [ 0 ] = n u m s [ 0 ] 只有一间房屋,则偷窃该房屋 d p [ 1 ] = m a x ( n u m s [ 0 ] , n u m s [ 1 ] ) 只有两间房屋,选择其中金额较高的房屋进行偷窃 \begin{cases} dp[0] = nums[0] & 只有一间房屋,则偷窃该房屋\\ dp[1] = max(nums[0], nums[1]) & 只有两间房屋,选择其中金额较高的房屋进行偷窃 \end{cases} {dp[0]=nums[0]dp[1]=max(nums[0],nums[1])只有一间房屋,则偷窃该房屋只有两间房屋,选择其中金额较高的房屋进行偷窃

最终的答案即为 d p [ n − 1 ] dp[n−1] dp[n1],其中 n n n 是数组的长度。

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0

        size = len(nums)
        if size == 1:
            return nums[0]
        
        dp = [0] * size
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])
        for i in range(2, size):
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
        
        return dp[size - 1]

上述方法使用了数组存储结果。考虑到每间房屋的最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组,在每个时刻只需要存储前两间房屋的最高总金额。

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0

        size = len(nums)
        if size == 1:
            return nums[0]
        
        first, second = nums[0], max(nums[0], nums[1])
        for i in range(2, size):
            first, second = second, max(first + nums[i], second)
        
        return second

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组长度。只需要对数组遍历一次。
  • 空间复杂度: O ( 1 ) O(1) O(1)。使用滚动数组,可以只存储前两间房屋的最高总金额,而不需要存储整个数组的结果,因此空间复杂度是 O ( 1 ) O(1) O(1)

打家劫舍 II

题目描述

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

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

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2),因为他们是相邻的。

示例 2:

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

示例 3:

输入:nums = [1,2,3]
输出:3

来源: 213. 打家劫舍 II - 力扣 (LeetCode)

题解

动态规划

首先考虑最简单的情况。如果只有一间房屋,则偷窃该房屋,可以偷窃到最高总金额。如果只有两间房屋,则由于两间房屋相邻,不能同时偷窃,只能偷窃其中的一间房屋,因此选择其中金额较高的房屋进行偷窃,可以偷窃到最高总金额。

注意到当房屋数量不超过两间时,最多只能偷窃一间房屋,因此不需要考虑首尾相连的问题。如果房屋数量大于两间,就必须考虑首尾相连的问题,第一间房屋和最后一间房屋不能同时偷窃。

如何才能保证第一间房屋和最后一间房屋不同时偷窃呢?如果偷窃了第一间房屋,则不能偷窃最后一间房屋,因此偷窃房屋的范围是第一间房屋到最后第二间房屋;如果偷窃了最后一间房屋,则不能偷窃第一间房屋,因此偷窃房屋的范围是第二间房屋到最后一间房屋。

假设数组 n u m s nums nums 的长度为 n n n。如果不偷窃最后一间房屋,则偷窃房屋的下标范围是 [ 0 , n − 2 ] [0,n−2] [0,n2];如果不偷窃第一间房屋,则偷窃房屋的下标范围是 [ 1 , n − 1 ] [1,n−1] [1,n1]。在确定偷窃房屋的下标范围之后,即可用第 198 题的方法解决。对于两段下标范围分别计算可以偷窃到的最高总金额,其中的最大值即为在 n n n 间房屋中可以偷窃到的最高总金额。

假设偷窃房屋的下标范围是 [ s t a r t , e n d ] [start,end] [start,end],用 d p [ i ] dp[i] dp[i] 表示在下标范围 [ s t a r t , i ] [start,i] [start,i] 内可以偷窃到的最高总金额,那么就有如下的状态转移方程:

d p [ i ] = max ⁡ ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) dp[i]=\max(dp[i−2]+nums[i],dp[i−1]) dp[i]=max(dp[i2]+nums[i],dp[i1])

边界条件为:

{ d p [ s t a r t ] = n u m s [ s t a r t ] 只有一间房屋,则偷窃该房屋 d p [ s t a r t + 1 ] = max ⁡ ( n u m s [ s t a r t ] , n u m s [ s t a r t + 1 ] ) 只有两间房屋,偷窃其中金额较高的房屋 \begin{cases} dp[start]=nums[start] & 只有一间房屋,则偷窃该房屋 \\ dp[start+1]=\max(nums[start],nums[start+1]) & 只有两间房屋,偷窃其中金额较高的房屋 \end{cases} {dp[start]=nums[start]dp[start+1]=max(nums[start],nums[start+1])只有一间房屋,则偷窃该房屋只有两间房屋,偷窃其中金额较高的房屋

计算得到 d p [ e n d ] dp[end] dp[end] 即为下标范围 [ s t a r t , e n d ] [start,end] [start,end] 内可以偷窃到的最高总金额。

分别取 ( s t a r t , e n d ) = ( 0 , n − 2 ) (start,end)=(0,n−2) (start,end)=(0,n2) ( s t a r t , e n d ) = ( 1 , n − 1 ) (start,end)=(1,n−1) (start,end)=(1,n1) 进行计算,取两个 d p [ e n d ] dp[end] dp[end] 中的最大值,即可得到最终结果。

根据上述思路,可以得到时间复杂度 O ( n ) O(n) O(n) 和空间复杂度 O ( n ) O(n) O(n) 的实现。考虑到每间房屋的最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组,在每个时刻只需要存储前两间房屋的最高总金额,将空间复杂度降到 O ( 1 ) O(1) O(1)

class Solution:
    def rob(self, nums: List[int]) -> int:
        def robRange(start: int, end: int) -> int:
            first = nums[start]
            second = max(nums[start], nums[start + 1])
            for i in range(start + 2, end + 1):
                first, second = second, max(first + nums[i], second)
            return second
        
        length = len(nums)
        if length == 1:
            return nums[0]
        elif length == 2:
            return max(nums[0], nums[1])
        else:
            return max(robRange(0, length - 2), robRange(1, length - 1))

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组长度。需要对数组遍历两次,计算可以偷窃到的最高总金额。
  • 空间复杂度: O ( 1 ) O(1) O(1)

打家劫舍 III

题目描述

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

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

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

示例 1:

img

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

示例 2:

img

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

来源: 337. 打家劫舍 III - 力扣 (LeetCode)

题解

动态规划

简化一下这个问题:一棵二叉树,树上的每个点都有对应的权值,每个点有两种状态(选中和不选中),问在不能同时选中有父子关系的点的情况下,能选中的点的最大权值和是多少。

我们可以用 f ( o ) f(o) f(o) 表示选择 o o o 节点的情况下, o o o 节点的子树上被选择的节点的最大权值和; g ( o ) g(o) g(o) 表示不选择 o o o 节点的情况下, o o o 节点的子树上被选择的节点的最大权值和; l l l r r r 代表 o o o 的左右孩子。

  • o o o 被选中时, o o o 的左右孩子都不能被选中,故 o o o 被选中情况下子树上被选中点的最大权值和为 l l l r r r 不被选中的最大权值和相加,即 f ( o ) = g ( l ) + g ( r ) f(o)=g(l)+g(r) f(o)=g(l)+g(r)
  • o o o 不被选中时, o o o 的左右孩子可以被选中,也可以不被选中。对于 o o o 的某个具体的孩子 x x x,它对 o o o 的贡献是 x x x 被选中和不被选中情况下权值和的较大值。故 g ( o ) = m a x { f ( l ) , g ( l ) } + m a x { f ( r ) , g ( r ) } g(o)=max\{f(l),g(l)\}+max\{f(r),g(r)\} g(o)=max{f(l),g(l)}+max{f(r),g(r)}

至此,我们可以用哈希表来存 f f f g g g 的函数值,用深度优先搜索的办法后序遍历这棵二叉树,我们就可以得到每一个节点的 f f f g g g。根节点的 f f f g g g 的最大值就是我们要找的答案。

我们不难给出这样的实现:

class Solution:
    def rob(self, root: Optional[TreeNode]) -> int:
        f = {None:0}
        g = {None:0}

        def dfs(node):
            if not node:
                return
            dfs(node.left)
            dfs(node.right)
            f[node] = node.val + g[node.left] + g[node.right]
            g[node] = max(f[node.left], g[node.left]) + max(f[node.right], g[node.right])

        dfs(root)
        return max(f[root], g[root])

假设二叉树的节点个数为 n n n

我们可以看出,以上的算法对二叉树做了一次后序遍历,时间复杂度是 O ( n ) O(n) O(n);由于递归会使用到栈空间,空间代价是 O ( n ) O(n) O(n),哈希表的空间代价也是 O ( n ) O(n) O(n),故空间复杂度也是 O ( n ) O(n) O(n)

我们可以做一个小小的优化,我们发现无论是 f ( o ) f(o) f(o) 还是 g ( o ) g(o) g(o),他们最终的值只和 f ( l ) f(l) f(l) g ( l ) g(l) g(l) f ( r ) f(r) f(r) g ( r ) g(r) g(r) 有关,所以对于每个节点,我们只关心它的孩子节点们的 f f f g g g 是多少。我们可以设计一个结构,表示某个节点的 f f f g g g 值,在每次递归返回的时候,都把这个点对应的 f f f g g g 返回给上一级调用,这样可以省去哈希表的空间。

代码如下:

class Solution:
    def rob(self, root: Optional[TreeNode]) -> int:
        def dfs(node):
            if not node:
                return (0, 0)
            l = dfs(node.left)
            r = dfs(node.right)
            selected = node.val + l[1] + r[1]
            notSelected = max(l) + max(r)
            return selected, notSelected

        ans = dfs(root)
        return max(ans)

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)。上文中已分析。
  • 空间复杂度: O ( n ) O(n) O(n)。虽然优化过的版本省去了哈希表的空间,但是栈空间的使用代价依旧是 O ( n ) O(n) O(n),故空间复杂度不变。

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

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

相关文章

python 网络编程和http协议--网络编程,HTTP协议,Web服务器

一.网络编程 1.IP地址 给网络中的每一台设备进行编号. IPV4 IPV6 2.端口和端口号 端口的作用就是给运行的应用程序提供传输数据的通道。 端口号的作用是用来区分和管理不同端口的,通过端口号能找到唯一个的一个端口。 3.TCP协议 协议: 双方的约定. 网络传输协…

基于SpringBoot+微信小程序的点餐系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景介绍: 小程序外卖扫码点餐为…

【MQTT】关于部署含有MQTT协议的程序pod到K8S中出现的问题

1、如何在go-zero整合MQTT协议 整合EMQX与eclipse/paho.mqtt.golang实现TLS(ssl)单向认证 订阅、发布、解压缩gzip、zlib数据报文 https://ctraplatform.blog.csdn.net/article/details/130525974 1.1 、通过钩子函数一直出现Lost问题排查 场景&#xff…

人类睡眠EEG分析:附代码实现的方法学入门

导读 近年来,人类睡眠脑电图(EEG)研究激增,采用了越来越复杂的分析策略将电生理活动与认知和疾病联系起来。然而,正确计算和解释当代睡眠EEG中使用的指标需要注意许多理论和实际的信号处理细节。本研究回顾了与频谱分析、蒙太奇选择、相位和…

VC GDI双缓冲绘图

VC GDI双缓冲绘图 VC GDI双缓冲绘图创建内存DC和内存图片,缺一不可最好是封装一下内存绘制绘制效果 关键是不闪烁PS 重绘机制 VC GDI双缓冲绘图 双缓冲绘图,知道这个知识点,每次用的时候还得踩一遍坑,真是服,总结记录…

BGP实验--联邦以及反射器

实验明细 实验拓扑实验要求实验内容 实验拓扑 实验要求 1.R2-R7每台路由器均存在一个环回接口用于建立邻居;同时还存在一个环回来代表连接用户的接口;最终这些连接用户的接口网络需要可以和R1/8的环回通讯 2.AS2网段地址为172.16.0.0/16,减少…

【开源项目】ShenYu网关中Disruptor的使用

模块封装 shenyu-disruptor定义了DisruptorProvider、DisruptorProviderManage、DataEvent、QueueConsumerFactory、DisrutporThreadFactory等一系列通用接口 该模块的搭建了一个disruptor的初始化框架, DisruptorProviderManage提供Disruptor的初始化,…

分布式事务的21种武器 - 4

在分布式系统中,事务的处理分布在不同组件、服务中,因此分布式事务的ACID保障面临着一些特殊难点。本系列文章介绍了21种分布式事务设计模式,并分析其实现原理和优缺点,在面对具体分布式事务问题时,可以选择合适的模式…

软件设计师数据结构速过

加法规则:多项相加,保留最高阶项,并将系数化为 1 乘法规则:多项相乘都保留,并将系数化为 1 加法乘法混合规则:先小括号再乘法规则最后加法规则 时间复杂度估算看最内层循环,如若没有循环和递归则…

终于!我们把 CEO 炒了,让 ChatGPT 出任 CEO

⚠️ FBI Warning:本文纯属作者自娱自乐,数字人的观点不代表 CEO 本人的观点,请大家不要上当受骗!! 哪个公司的 CEO 不想拥有一个自己的数字克隆? 想象🤔一下,如果 CEO 数字克隆上线…

【初识django】——django——如桃花来

目录索引 django引入:常见的web框架:下载问题:*下载Django之前确保工具不会发生版本问题*下载django:*检查是否下载成功:**注意事项:* 创建django项目:在cmd中创建:*整个命令流程:**…

React学习笔记六-refs

此文章是本人在学习React的时候,写下的学习笔记,在此纪录和分享。此为第六篇,主要介绍react中的refs。 目录 1.refs基本使用 1.1字符串类型ref小案例 2.回调形式的ref 2.1回调形式ref小案例 2.2回调ref中调用次数问题 3.createRef 3.…

SpringBoot 插件 spring-boot-maven-plugin 原理,以及SpringBoo工程部署的 jar 包瘦身实战

spring-boot-maven-plugin 我们直接使用 maven package (maven自带的package打包功能),打包Jar包的时候,不会将该项目所依赖的Jar包一起打进去,在使用java -jar命令启动项目时会报错,项目无法正常启动。这…

TOP RPA·脱普×实在丨日用品企业脱普签约实在智能,构建全域数据智能运营系统

近日,实在智能与脱普日用化学品(中国)有限公司(简称“脱普企业”)在脱普企业上海总部举行“全域数据智能运营”项目启动会,双方领导及项目组关键成员共同参会,就项目目标、实施进程、沟通机制等…

Spring Boot中使用Spring Data Elasticsearch访问Elasticsearch

Spring Boot中使用Spring Data Elasticsearch访问Elasticsearch Elasticsearch是一个分布式的全文搜索和分析引擎,它可以将海量数据进行快速的查询和聚合。Spring Data Elasticsearch是Spring Data家族中的一个成员,它提供了与Elasticsearch的集成&…

一起来学习怎样识别表格文件吧

你有没有经历过手头有一堆纸质表格,但是又不想手动输入数据的烦恼?现在,表格识别计数的出现,可以帮助你轻松解决这个问题。它通过拍照扫描,来自动提取表格中的信息,并将其转化为可编辑的电子文档。那么&…

c# 动态表达式

准备: 创建一个空项目,nuget查找并安装ExpressionEvaluator 示例: using ExpressionEvaluator; using System; 一、计算简单表达式 public string Test1() { return SimpleEval("0.1*(Math.Pow(10,2)20)"); …

AI小作文搞崩科大讯飞股价 科技“魔法”反噬科企

5月24日午后,A股公司科大讯飞的股价突然走出深V造型,闪崩8%。科大讯飞回应称,股价下跌系某生成式AI写作虚假小作文导致,谣传风险为不实消息。 网传的一篇“小作文”谣称“科大讯飞被曝采集用户隐私数据研究人工智能引发争议”&am…

Windows下编写的shell脚本无法在Linux上执行

这通常是由于回车换行符不兼容导致的。 出现无法执行,提示诸如“ 未预期的符号“$\r”附近有语法错误”,“syntax error near unexpected token in”之类的错误,可尝试此文方法。 1.查看shell脚本的换行符格式 vi/vim进入文件,…

2023年湖北建筑起重信号司索工报名流程是什么?个人可以报名吗?

2023年湖北建筑起重信号司索工报名流程是什么?个人可以报名吗? 建筑起重信号司索工是特种作业人员工种即是建设厅特种工。证书全国通用,两年需要年审一次,六年需要换一次证。报考有一定的条件和要求。搜一下启程别就知道啦。 湖北…