You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LeetCode 1155 字典记忆化与LRU Cache性能差异原因求解

字典记忆化搜索与lru_cache的性能差异原因分析

问题背景

我在求解LeetCode 1155「掷骰子的和等于目标值的数目」问题时,首先实现了基于字典的记忆化方案,代码如下:

class Solution:
    def numRollsToTarget(self, dices: int, faces: int, target: int) -> int:
        
        dp = {}
        def ways(t, rd):
            if t == 0  and rd == 0: return 1
            if t <= 0 or rd <= 0: return 0
            if dp.get((t,rd)): return dp[(t,rd)]
            dp[(t,rd)] = sum(ways(t-i, rd-1) for i in range(1,faces+1))
            return dp[(t,rd)]
        
        return ways(target, dices)

该解法在骰子数、面数均为15左右的测试用例上固定出现超时。后续我找到了使用functools.lru_cache实现缓存的解法,除缓存实现逻辑外其余代码完全一致,运行速度非常快,代码如下:

class Solution:
    def numRollsToTarget(self, dices: int, faces: int, target: int) -> int:
        from functools import lru_cache
        @lru_cache(None)
        def ways(t, rd):
            if t == 0  and rd == 0: return 1
            if t <= 0 or rd <= 0: return 0
            return sum(ways(t-i, rd-1) for i in range(1,faces+1))
        
        return ways(target, dices)

此前我对比过两种缓存实现,多数场景下lru_cache不会比字典缓存的性能领先这么多,因此好奇两者出现如此大性能差距的具体原因。

原因分析

  • 核心原因是字典记忆化的判断逻辑存在bug,导致缓存完全失效
    你编写的if dp.get((t,rd)): return dp[(t,rd)]逻辑存在漏洞:dict.get()方法在键不存在时返回None,键存在时返回对应值,而这行代码的判断条件是「返回值为真」才会走缓存分支。本题中存在大量ways(t, rd)返回0的合法情况(比如剩余骰子数不够凑出目标值、目标值超过剩余骰子能扔出的最大值等),这些结果存入字典后,查询时返回的0会被判定为假,导致不会走缓存分支,每次都要重新递归计算,相当于缓存完全没发挥作用,性能自然极差。
    正确的字典判断写法应该是if (t, rd) in dp: return dp[(t, rd)],只要键存在就直接返回对应值,不管值是0还是其他数值,修改后性能会和lru_cache版本处于同一量级。
  • 次要原因是lru_cache的底层实现优化
    functools.lru_cache是Python标准库中用C语言实现的装饰器,参数哈希、缓存读写、函数调用的逻辑都经过底层优化,比纯Python手写的字典缓存+判断逻辑的执行效率更高,不过这个优化幅度通常在2~5倍区间,远低于修复逻辑bug带来的几十上百倍的性能提升。

内容的提问来源于stack exchange,提问作者sprksh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 15:27:00