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

动态规划入门:从记忆化搜索到递推

本篇笔记基于b站灵茶山艾府。

198. 打家劫舍

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

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

示例 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 。

递归

我们可以 将大问题转换为规模更小的子问题

比如从第一个房子或者最后一个房子开始思考,下面从最后一个房子举例。

考虑最后一个房子是选还是不选

  • 如果不选,问题就变成前n-1个房子的问题
  • 如果选,问题就变成前n-2个房子的问题

从最后一个房子思考到第一个房子,就可以得到一棵二叉搜索树了:

在这里插入图片描述

当确定当前是选还是不选时,就确定下一次递归参数中的 i

dfs( i )的含义就是从前i个房子中得到的最大金额,如果不选当前第i个房子,问题就变成前i-1个房子的最大金额,也就是dfs( i - 1 ),如果选,问题就变成前i-2个房子的最大金额加上当前房子的金额,也就是dfs( i - 2 )+nums[i],同时,返回值就是前i个房子的最大金额。

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        def dfs(i):
            # 递到空节点了,返回0
            if i < 0:
                return 0
            return max(dfs(i - 1), dfs(i - 2) + nums[i])
        return dfs(n - 1)

也可以从第一个房子开始:

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        def dfs(i):
            if i >= n:
                return 0
            return max(dfs(i + 1), dfs(i + 2) + nums[i])
        return dfs(0)

运行结果:

在这里插入图片描述

由于时间复杂度是指数级别的,所以毫无疑问会超时

所以我们考虑不必要的重复计算,从而引出记忆化搜索


记忆化搜索

在计算dp[i]时,我们需要dp[i-1]与dp[i-2]的信息,而在计算dp[i-1]的信息时,由于已经用到了dp[i-2](因为计算dp[i-1]需要dp[i-2]与dp[i-3]的信息),所以dp[i-2]对于dp[i]来说已经 可以 是已知的了,所以不需要再次重复地计算dp[i-2],此时可以用一个memory数组来存储已经计算过的信息。

在这里插入图片描述

由于只有n个节点,时间复杂度也从指数级别降到了O( n )

对于python可以用cache装饰器:

cache 装饰器是 functools 模块中提供的一个工具,用于实现记忆化(Memoization)。它会自动缓存函数的返回值。当函数被多次调用时,如果输入参数相同,它会直接返回缓存的结果,而不会重新计算。

class Solution:
    def rob(self, nums: List[int]) -> int:
        from functools import cache
        n = len(nums)
        @cache	# 使用cache装饰器
        def dfs(i):
            # 递到空节点了,返回0
            if i < 0:
                return 0
            return max(dfs(i - 1), dfs(i - 2) + nums[i])
        return dfs(n - 1)

在这里插入图片描述

也可以自己实现:

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        cache = [-1] * n  # 答案不可能是负数
        def dfs(i):
            # 递到空节点了,返回0
            if i < 0:
                return 0
            # 如果记忆数组当前值不为-1,说明之前已经被赋值过,直接返回值,不需要重新计算
            if cache[i] != -1:
                return cache[i]
            res = max(dfs(i - 1), dfs(i - 2) + nums[i])
            cache[i] = res
            return res
        return dfs(n - 1)

在这里插入图片描述

从cache数组来看空间复杂度为O ( n ) ,有没有办法可以将空间复杂度优化到O(1)?

递推

在记忆化搜索中,我们从dfs( i )递到dfs( i - 1 )和dfs( i - 2 ),再从dfs( i - 1 )递到dfs( i - 2 )和dfs( i - 3 )…逐渐递到dfs( 0 ),再从dfs( 0 )直接归到dfs( i ),但是既然我们已经知道要从dfs(0)归到dfs( i ),不如 去掉递,直接归 ,直接从最下面开始计算,这就是递推。

将记忆化搜索改成递推很简单,只需要将dfs函数改成dp数组,递归的过程改成循环,递归的边界就用dp数组初始值代替。

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 1:
            return nums[0]
        dp = [0] * n  # 记录从下归到上的过程中的值
        dp[0] = nums[0]  # 初始化,对应dfs函数中判断边界的部分
        dp[1] = max(nums[0], nums[1])
        for i in range(2, n):
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])  # 对应归的过程
        return dp[-1]

在这里插入图片描述

dp [ i ] 的含义就是前i个房子的最大价值

但是我们每次计算当前dp[i]的值,实际只需用到dp[i-1]与dp[i-2]的值,除此之外前面的值也是只用过一次就不要了,所以可以每次只维护三个值:dp[i]、dp[i-1]、dp[i-2],从而将空间复杂度降为 O(1)

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 1:
            return nums[0]
        i = nums[0]  # 对应dp[i-2]
        j = max(nums[0], nums[1])  # 对应dp[i-1]
        for x in range(2, n):
            k = max(i + nums[x], j)  # 对应dp[i]
            i = j
            j = k
        return k

相关文章:

  • 华为IP(3)
  • 去中心化借贷机制解析
  • MySQL 进阶 面经级
  • Oracle 数据库中优化 INSERT INTO 操作的性能
  • 量子计算与人工智能融合的未来趋势
  • 预训练(Pre-training) 和 微调(Fine-tuning)
  • 机器学习(总节环节)
  • 迈向云原生:理想汽车 OLAP 引擎变革之路
  • 【家政平台开发(6)】筑牢家政平台安全防线:全方位隐私与安全需求解析
  • 根据模板将 Excel 明细数据生成 Txt 文档|邮件合并
  • java的引用理解
  • 【行测】判断推理:逻辑判断
  • 4.1-python操作wrod/pdf 文件
  • Pytorch实现之基于GAN+序列后向选择的情绪识别增强方法
  • PyInstaller打包实战:如何智能切换本地调试与EXE模式,资源打包全攻略
  • MySQL数据库学习笔记1.SQL(1)
  • aarch64-none-elf-gcc与aarch64-linux-gnu-gcc
  • 源码分析之Leaflet中control模块Zoom类实现原理
  • 第二十二章:Python-NLTK库:自然语言处理
  • Vue3.5 企业级管理系统实战(十二):组件尺寸及多语言实现
  • 老字号“逆生长”,上海制造的出海“蜜”钥
  • 基金经理调仓引发大金融板块拉升?公募新规落地究竟利好哪些板块
  • 证券时报:中美互降关税落地,订单集中补发港口将迎高峰期
  • “一百零一个愿望——汉字艺术展”亮相意大利威尼斯
  • 5吨煤炭“瞬间蒸发”?掺水炭致企业损失千万,腐败窝案曝光
  • 男子不满和睦家医院手术效果还遇到了“冒牌医生”?院方回应