用lru_cache提高性能

本文探讨了lru_cache在动态规划中的应用,通过对比不同算法解决LeetCode题目,如爬楼梯和组合总和IV,展示了lru_cache如何优化递归函数的性能,同时也讨论了递归深度限制的问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

leetcode 上有一题爬楼梯的题,一个n阶的台阶,每次可爬1阶或2阶,问有多少中爬法。这道题不难,就是一个斐波那契数列。我用循环写的,没啥问题。然后看评论里有人用递归写,说会超时。然后有人用了lru_cache装饰器来提高性能,顺利通过。

lru即least recently used,lru_cache可以记录函数的调用结果,再次使用时直接使用之前的返回值,而不真的再次调用。
注意,被lru_cache装饰的函数,其参数必须是可哈希的

from functools import lru_cache
import time

@lru_cache(maxsize=5)
def a(i):
    print(i)

t=time.time()
for i in [1,2,3,4,5]*1000:
    a(i)
t=time.time()-t
print(t)

以上程序输出为:
在这里插入图片描述
可以看到,a(i)实际只执行了5次。lru_cache 的参数maxsize 代表能缓存几个函数执行的结果。

lru_cache容易让人联想到带备忘的动态规划,有了lru_cache,岂不是可以直接写普通的递归函数来达到类似动态规划的效果了?事实是,有时候还是要注意的!

比如,leetcode 第377题“组合总和IV”:
在这里插入图片描述
由于“组合总和”前几题都是用回溯法,这道题我一开始也用回溯法做的:

class Solution:
    def __init__(self):
        self.res = 0
        self.target = 0
        self.temp = []

    def backTrace(self, cans):
        s = sum(self.temp)
        for x in cans:
            if s+x == self.target:
                self.temp.append(x)
                self.res += 1  
                self.temp.pop()
            elif s+x < self.target:
                self.temp.append(x)
                self.backTrace(cans)
                self.temp.pop()
            else:
                break
        
    def combinationSum4(self, candidates, target):
        candidates.sort()
        self.target = target
        self.backTrace(candidates)
        return self.res

然而超时了。。。那么就用动态规划吧。想到lru_cache,我就自顶向下直接递归了:

from functools import lru_cache

class Solution:
 @lru_cache()
 def combinationSum(self, candidates, target):
     if len(candidates)==0:
         return 0
     if target == 0:
         return 1
     if target < min(candidates):
         return 0
     r = 0
     for x in candidates:
         r += self.combinationSum(candidates,target-x)
     return r
 
 def combinationSum4(self, candidates, target):
         cans = tuple(candidates)
         return self.combinationSum(cans, target)

然而还是超时了。。。在自己电脑上跑了下,提示“RecursionError: maximum recursion depth exceeded in comparison”,原来是超过最大递归层数了。。。。查了下最后的测试用例,candidates=[3,33,333],target=10000,确实递归层数太深了。所以还是老老实实自己写了自底向上的动态规划算法:

class Solution:
    def combinationSum4(self, candidates, target):
        men = [0]*(target+1)
        for i in range(target+1):
            if i in candidates:
                men[i] += 1
            for x in candidates:
                if i - x >= 0:
                    men[i] += men[i-x]
        return men[target]
<think>嗯,用户想了解如何使用Python的functools.lru_cache装饰器。首先,我需要回忆一下lru_cache的基本功能和用法。记得这个装饰器是用来缓存函数调用结果的,特别是对于那些计算量大的函数,可以避免重复计算,提高性能。根据引用[4],它是基于最近最少使用策略的缓存,当缓存满了之后会自动移除最久未使用的项。 然后,用户可能想知道如何正确使用它。基本用法应该是在函数定义前加上@lru_cache装饰器。不过要注意函数的参数必须是可哈希的,比如不可变类型,像整数、字符串、元组等。如果参数里有不可哈希的类型,比如列表,就会报错,如引用[3]中的例子,因为列表是不可哈希的,所以需要将参数改为元组或者其它可哈希类型。 接下来,可能需要解释如何设置缓存的大小。maxsize参数用来指定缓存的最大容量,如果设为None的话,缓存可以无限制增长,不过可能会消耗较多内存。另外,typed参数如果设置为True,会区分参数的类型,比如3和3.0会被当作不同的参数处理。 用户可能还会遇到缓存失效的问题,所以需要说明如何手动清除缓存,比如使用cache_clear()方法。或者需要动态调整maxsize的话,可以重新装饰函数。 然后,应该举几个例子,比如斐波那契数列的递归优化,用lru_cache来缓存中间结果,避免重复计算。代码示例需要正确展示装饰器的使用,并说明加上之后性能的提升,如引用[1]和[2]提到的。 还要提醒用户常见的错误,比如参数不可哈希的情况,以及如何解决。例如,将列表参数转换为元组,或者在函数内部处理可变参数。 最后,生成相关问题,帮助用户进一步学习,比如如何调整缓存大小,处理不可哈希参数,性能优化的实际案例,以及与其他缓存装饰器的比较。 现在需要把这些点整理成一个结构清晰的回答,确保每个部分都有对应的引用标注,并且代码示例正确,符合LaTeX格式要求。同时,要使用中文,保持自然流畅,避免使用Markdown格式,行内公式用$包裹,独立公式用$$。还要检查是否有遗漏的注意事项,比如typed参数的用法,或者缓存的线程安全等问题,但根据现有引用,可能不需要深入线程问题,保持基础即可。</think>### Python functools.lru_cache 装饰器使用详解 #### 1. 基本概念 `functools.lru_cache` 是 Python 标准库提供的装饰器,用于缓存函数计算结果,特别适用于**重复调用且计算成本高**的函数[^4]。其核心机制是最近最少使用(LRU)策略,当缓存达到指定大小时,自动淘汰最久未使用的缓存项。 #### 2. 基础用法 ```python from functools import lru_cache @lru_cache(maxsize=128, typed=False) def calculate(a: int, b: float) -> float: print("执行计算...") # 仅第一次调用时打印 return a * b + (a - b) ``` - **`maxsize`**: 缓存容量,默认 128。设为 `None` 表示无限制(可能内存耗尽)。 - **`typed`**: 若为 `True`,则区分参数类型(如 `3` 和 `3.0` 视为不同参数)。 #### 3. 典型场景:优化递归函数 以斐波那契数列为例,未使用缓存时时间复杂度为 $O(2^n)$,使用后降为 $O(n)$: ```python @lru_cache(maxsize=None) def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) ``` #### 4. 注意事项 - **参数必须可哈希**:若参数含列表等不可哈希类型,需转换为元组(如 `tuple(nums)`)或调整设计[^3]。 - **缓存手动清除**:通过 `fibonacci.cache_clear()` 清空缓存。 - **线程安全**:在并发场景下需自行加锁。 #### 5. 错误处理示例 ```python # 错误用法:参数含列表 @lru_cache def sum_list(nums): return sum(nums) sum_list([1, 2, 3]) # 报错:TypeError: unhashable type: 'list' # 正确改进:将列表转为元组 @lru_cache def sum_list_tuple(nums_tuple): return sum(nums_tuple) sum_list_tuple(tuple([1, 2, 3])) # 正常执行 ``` #### 6. 性能对比 引用测试案例:使用 `lru_cache` 后,某递归函数运行时间从 12.3 秒缩短至 0.4 秒[^1]。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值