动态规划中的记忆化搜索技术详解

动态规划中的记忆化搜索技术详解

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

什么是记忆化搜索

记忆化搜索(Memoization Search)是一种优化递归算法的技术,它通过存储已经计算过的子问题结果来避免重复计算,从而显著提高算法效率。这种技术本质上是动态规划的一种实现方式,采用"自顶向下"的解决问题思路。

记忆化搜索的核心思想可以概括为:"计算一次,存储结果,多次复用"。当算法需要计算某个子问题时,它首先检查是否已经计算过该问题。如果已经计算过,则直接返回存储的结果;否则进行计算,并将结果存储下来以备后续使用。

记忆化搜索的工作原理

让我们以经典的斐波那契数列为例来说明记忆化搜索的工作原理。斐波那契数列的定义是:

  • f(0) = 0
  • f(1) = 1
  • f(n) = f(n-1) + f(n-2) (当n≥2时)

如果使用普通递归算法计算f(5),调用过程如下:

  1. 计算f(5)需要f(4)和f(3)
  2. 计算f(4)需要f(3)和f(2)
  3. 计算f(3)需要f(2)和f(1)
  4. 计算f(2)需要f(1)和f(0)

可以看到,f(3)被计算了两次,f(2)被计算了三次,f(1)和f(0)被计算的次数更多。这种重复计算导致普通递归算法的时间复杂度呈指数级增长。

记忆化搜索通过保存已计算的斐波那契数来解决这个问题。具体实现如下:

class Solution:
    def fib(self, n: int) -> int:
        # 使用数组保存已经求解过的f(k)的结果
        memo = [0 for _ in range(n + 1)]
        return self.my_fib(n, memo)

    def my_fib(self, n: int, memo: List[int]) -> int:
        if n == 0:
            return 0
        if n == 1:
            return 1
        
        # 已经计算过结果
        if memo[n] != 0:
            return memo[n]
        
        # 没有计算过结果
        memo[n] = self.my_fib(n - 1, memo) + self.my_fib(n - 2, memo)
        return memo[n]

在这个实现中,我们使用一个数组memo来存储已经计算过的斐波那契数。每次计算f(n)之前,先检查memo[n]是否已经被计算过,如果是则直接返回存储的值,否则进行计算并存储结果。

记忆化搜索与递推的区别

记忆化搜索和递推都是动态规划的常见实现方式,但它们有显著的区别:

| 特性 | 记忆化搜索 | 递推 | |-----------|----------------------------|----------------------------| | 方向 | 自顶向下(从问题分解到子问题) | 自底向上(从基础子问题构建到原问题) | | 实现方式 | 递归 | 循环 | | 计算顺序 | 按需计算 | 预先计算所有可能需要的子问题 | | 优点 | 代码直观,只计算必要的子问题 | 没有递归开销,计算顺序明确 | | 缺点 | 递归深度大时可能栈溢出 | 可能计算不必要的子问题 | | 适用场景 | 状态转移复杂,子问题不是全部需要的情况 | 状态转移简单,需要计算所有子问题的情况 |

选择使用记忆化搜索还是递推,应根据具体问题的特点和约束条件来决定。

记忆化搜索的解题步骤

使用记忆化搜索解决问题通常遵循以下步骤:

  1. 定义状态和状态转移方程:明确问题的状态表示和状态之间的转移关系。
  2. 设计缓存结构:选择合适的数据结构(通常是数组或哈希表)来存储子问题的解。
  3. 实现递归函数
    • 基本情况处理:定义递归的终止条件。
    • 缓存检查:在计算前检查是否已有缓存结果。
    • 递归计算:按状态转移方程进行递归计算。
    • 结果缓存:将计算结果存入缓存。
  4. 调用递归函数:从初始状态开始调用递归函数。

记忆化搜索的实际应用

应用1:目标和问题

问题描述:给定一个整数数组nums和一个整数target。向数组中每个整数前加'+'或'-',然后串联起来构造成一个表达式。返回运算结果等于target的不同表达式数目。

记忆化搜索解决方案

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        size = len(nums)
        table = dict()  # 使用字典作为缓存

        def dfs(i, cur_sum):
            if i == size:
                return 1 if cur_sum == target else 0
                    
            if (i, cur_sum) in table:  # 检查缓存
                return table[(i, cur_sum)]
            
            # 递归计算两种选择的结果
            cnt = dfs(i + 1, cur_sum - nums[i]) + dfs(i + 1, cur_sum + nums[i])
            table[(i, cur_sum)] = cnt  # 存储结果到缓存
            return cnt

        return dfs(0, 0)

在这个解决方案中,我们使用一个字典table来缓存已经计算过的状态(i, cur_sum)的结果。这避免了重复计算相同的状态,大大提高了效率。

应用2:泰波那契数列

问题描述:计算第n个泰波那契数,定义如下:

  • T0 = 0
  • T1 = 1
  • T2 = 1
  • Tn+3 = Tn + Tn+1 + Tn+2 (当n≥0时)

记忆化搜索解决方案

class Solution:
    def tribonacci(self, n: int) -> int:
        memo = [0] * (n + 1)  # 初始化缓存数组
        return self.my_tribonacci(n, memo)
    
    def my_tribonacci(self, n: int, memo: List[int]) -> int:
        if n == 0:
            return 0
        if n == 1 or n == 2:
            return 1
        
        if memo[n] != 0:  # 检查缓存
            return memo[n]
        
        # 递归计算并存储结果
        memo[n] = self.my_tribonacci(n - 3, memo) + \
                 self.my_tribonacci(n - 2, memo) + \
                 self.my_tribonacci(n - 1, memo)
        return memo[n]

这个实现使用数组memo来缓存已经计算过的泰波那契数,避免了重复计算,将时间复杂度从指数级降低到线性级。

记忆化搜索的优化技巧

  1. 选择合适的缓存数据结构

    • 对于状态参数是连续整数的情况,使用数组通常更高效。
    • 对于状态参数不连续或较复杂的情况,使用字典更合适。
  2. 状态表示优化

    • 尽量简化状态表示,减少缓存的大小。
    • 有时可以通过数学变换减少状态参数的数量。
  3. 边界条件处理

    • 确保正确处理所有边界条件,避免无限递归。
    • 对不可能达到的状态进行剪枝。
  4. 空间优化

    • 对于某些问题,可以只缓存必要的前几个状态,而不是所有状态。

常见问题与解决方案

  1. 栈溢出问题

    • 原因:递归深度过大。
    • 解决方案:改用递推方法或增加递归深度限制。
  2. 缓存效率低

    • 原因:缓存命中率低或缓存数据结构选择不当。
    • 解决方案:优化状态表示,选择更合适的缓存结构。
  3. 时间复杂度过高

    • 原因:状态转移方程复杂或状态空间过大。
    • 解决方案:寻找更优的状态表示或算法。

记忆化搜索是动态规划中非常实用的技术,特别适合状态转移复杂但不需要计算所有子问题的情况。掌握这项技术可以显著提高解决复杂问题的能力。

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蒙丁啸Sharp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值