doocs/leetcode 记忆化搜索:算法优化的利器
还在为递归算法的时间复杂度爆炸而头疼吗?还在重复计算相同的子问题而浪费宝贵的计算资源?记忆化搜索(Memoization)正是解决这类问题的利器!本文将深入解析 doocs/leetcode 项目中记忆化搜索的实现技巧和应用场景。
什么是记忆化搜索?
记忆化搜索是一种优化技术,通过存储已计算的结果来避免重复计算。它本质上是动态规划与深度优先搜索的完美结合,既保持了递归的直观性,又获得了动态规划的高效性。
核心思想
doocs/leetcode 中的记忆化搜索实现
1. 使用 @cache 装饰器
Python 3.9+ 提供了内置的 functools.cache 装饰器,让记忆化变得异常简单:
from functools import cache
class Solution:
def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
@cache
def dfs(i: int, j: int) -> int:
ans = 0
for a, b in pairwise((-1, 0, 1, 0, -1)):
x, y = i + a, j + b
if 0 <= x < m and 0 <= y < n and matrix[x][y] > matrix[i][j]:
ans = max(ans, dfs(x, y))
return ans + 1
m, n = len(matrix), len(matrix[0])
return max(dfs(i, j) for i in range(m) for j in range(n))
2. 经典应用场景分析
场景一:矩阵中的最长递增路径(LeetCode 329)
| 技术要点 | 说明 |
|---|---|
| 问题类型 | 图遍历 + 动态规划 |
| 记忆化键 | (i, j) 坐标对 |
| 时间复杂度 | 从 O(2^(mn)) 优化到 O(mn) |
| 空间复杂度 | O(m*n) |
场景二:统计所有可行路径(LeetCode 1575)
class Solution:
def countRoutes(
self, locations: List[int], start: int, finish: int, fuel: int
) -> int:
@cache
def dfs(i: int, k: int) -> int:
if k < abs(locations[i] - locations[finish]):
return 0
ans = int(i == finish)
for j, x in enumerate(locations):
if j != i:
ans = (ans + dfs(j, k - abs(locations[i] - x))) % mod
return ans
mod = 10**9 + 7
return dfs(start, fuel)
记忆化搜索 vs 传统动态规划
对比分析表
| 特性 | 记忆化搜索 | 传统动态规划 |
|---|---|---|
| 实现方式 | 自顶向下 | 自底向上 |
| 代码可读性 | 高(递归形式) | 中等(迭代形式) |
| 计算顺序 | 按需计算 | 全部计算 |
| 适用场景 | 状态空间不规则 | 状态空间规则 |
| 初始化 | 无需显式初始化 | 需要显式初始化 |
选择指南
实战技巧与最佳实践
1. 键的设计原则
记忆化搜索的核心在于键(Key)的设计,好的键设计能显著提升性能:
# 好的键设计:使用元组包含所有影响结果的状态
@cache
def dfs(x, y, remaining): ...
# 避免的键设计:包含可变对象或复杂结构
@cache
def dfs(matrix_state): ... # 错误!矩阵状态太大
2. 边界条件处理
@cache
def recursive_func(params):
# 1. 检查基础情况
if base_case(params):
return base_value
# 2. 检查无效情况
if invalid_case(params):
return sentinel_value
# 3. 递归计算并缓存
result = compute(params)
return result
3. 性能优化策略
| 策略 | 描述 | 效果 |
|---|---|---|
| 提前剪枝 | 在递归前判断是否必要 | 减少不必要的递归调用 |
| 状态压缩 | 减少键的维度 | 降低空间复杂度 |
| 懒计算 | 只在需要时计算 | 避免不必要的计算 |
常见问题与解决方案
问题1:递归深度过大
解决方案:使用迭代加深或转换为迭代动态规划
问题2:内存占用过高
解决方案:使用 LRU Cache 或自定义缓存策略
from functools import lru_cache
@lru_cache(maxsize=10000)
def expensive_function(params):
# 函数实现
问题3:键冲突
解决方案:确保键的唯一性和一致性
进阶应用:数位DP中的记忆化搜索
数位DP(Digit DP)是记忆化搜索的经典应用领域,用于解决数字相关的计数问题:
@cache
def digit_dp(pos: int, tight: bool, state: int) -> int:
if pos == len(digits):
return 1 if valid(state) else 0
limit = int(digits[pos]) if tight else 9
result = 0
for digit in range(limit + 1):
new_tight = tight and (digit == limit)
new_state = update_state(state, digit)
result += digit_dp(pos + 1, new_tight, new_state)
return result
总结
记忆化搜索是算法竞赛和面试中的必备技能,它巧妙地将递归的直观性与动态规划的高效性相结合。通过 doocs/leetcode 项目的实际案例,我们可以看到:
- @cache 装饰器让记忆化实现变得异常简单
- 合理的键设计是性能优化的关键
- 适用于不规则状态空间的问题
- 与数位DP等高级技巧完美结合
掌握记忆化搜索,你就能在算法之路上走得更远,轻松应对各种复杂的递归优化问题!
提示:在实际编码中,记得根据具体问题特点选择合适的记忆化策略,并注意内存使用情况。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



