9月19日 每日一题 650. 只有两个键的键盘。递归,装饰器(lru_cache),质因数分解

我一开始的思路是用递归解决:

class Solution:
    def minSteps(self, n: int) -> int:
        @functools.lru_cache()
        def findprime(n):
            if n==1:
                return 0
            for i in range(int(sqrt(n)),1,-1):
                if n%i==0:
                    return findprime(i)+findprime(n//i)
            return n
        return findprime(n)

时间复杂度大概是根号n左右,这里用到了一个记忆化递归的手法。 先讲一下cache装饰器。具体的知识我看这篇文章很有收获:
缓存淘汰算法与 python 中 lru_cache 装饰器的实现

基本思想是,缓存是有限的,那么如何选择被淘汰的缓存呢?
缓存淘汰算法:
FIFO:先进先出,太简单,不行
LFU: 但是,这个算法仍然存在一定的问题,那就是一旦某个数据在短时间被大量访问,此后即便很长时间没有任何访问,该数据仍然凭借其巨大的访问次数数值而不被淘汰。
LRU:最常使用:这是目前实践中最为广泛使用的一种缓存淘汰算法,他的算法思想与 LFU 非常相似,但他去除了访问次数数值,在队列中采用访问则上升的策略,从而规避了上面提到的访问次数数值过大造成的影响。

讲到装饰器,就要先从装饰器的原理开始:具体看文章:
让你真正明白装饰器的工作原理和执行顺序
简单来说:python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。
而lru_cache本质上是一种带参数的装饰器,他的参数就是max_size:

def make_key(args, kwds):
    """
    通过方法参数获取缓存的 key
    :param args: 方法原始参数
    :param kwds: 方法键值对参数
    :return: 计算后 key 的 hash 值
    """
    key = args
    if kwds:
        key += object()
        for item in kwds.items():
            key += item
    elif len(key) == 1 and type(key[0]) in {int, str, frozenset, type(None)}:
        return key[0]
    return hash(key)


def lru_cache(maxsize=128):
    def decorating_function(user_function):
        PREV, NEXT, KEY, RESULT = 0, 1, 2, 3

        cache = {}                          # 缓存结构,存储 key 与缓存数据的映射
        hits = misses = 0                   # 缓存命中、丢失统计
        full = False                        # 缓冲区是否已满标记
        cache_len = cache.__len__           # 缓冲区大小
        root = []
        root[:] = [root, root, None, None]  # 队列头结点为空

        def lru_cache_wrapper(*args, **kwds):
            nonlocal root, hits, misses, full
            key = make_key(args, kwds)
            with RLock():  # 线程安全锁
                link = cache.get(key)

                """ 缓存命中,移动命中节点,直接返回预设结果 """
                if link is not None:
                    """ 从链表中移除命中节点 """
                    link_prev, link_next, _key, result = link
                    link_prev[NEXT] = link_next
                    link_next[PREV] = link_prev

                    """ 将命中节点移动到队尾 """
                    last = root[PREV]
                    last[NEXT] = root[PREV] = link
                    link[PREV] = last
                    link[NEXT] = root
                    hits += 1
                    return result

            """ 缓存未命中,调用方法获取返回,创建节点,淘汰算法 """
            result = user_function(*args, **kwds)
            with RLock():
                if key in cache:
                    pass
                elif full:
                    """ 缓冲区已满,淘汰队首元素,删除缓冲区对应元素 """
                    oldroot = root
                    oldroot[KEY] = key
                    oldroot[RESULT] = result
                    root = oldroot[NEXT]
                    oldkey = root[KEY]
                    root[KEY] = root[RESULT] = None
                    del cache[oldkey]
                    cache[key] = oldroot
                else:
                    """ 缓冲区未满,直接创建节点,插入数据 """
                    last = root[PREV]
                    link = [last, root, key, result]
                    last[NEXT] = root[PREV] = cache[key] = link
                    full = (cache_len() >= maxsize)
                misses += 1
            return result
        return lru_cache_wrapper

    return decorating_function

关于带parameter的decorator我看了看中文的博客没有讲的很好的,不过GF上有一篇文章讲的不错:
Decorators with parameters in Python

稍微聊聊一下装饰器,然后再扯回原来的方法,官方题解给的方法是质因数分解的方法,方法一般但具有普遍性,所以我自己又写了一遍。

class Solution:
    def minSteps(self, n: int) -> int:
        res,i=0,2
        while i*i<=n:
            while n%i==0:
                n=n//i
                res+=i
            i+=1
        if n!=1:
            return res+n
        return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值