爬楼梯问题,yield学习总结

时间:2022-03-02 23:30:52

问题起源:

一个人爬楼梯,一步可以迈一级,二级,如果楼梯有N级,要求编写程序,求总共有多少种走法。

简单的一个递归思想,只要爬到了N-1层,或者爬到N-2层,则认定下一步只有一种走法。所以再去找寻N-1、N-2的前两步。动态规划方程为:f(n) = f(n-1) +f(n-2)

 

方案1:

def climb_2(n):  # 速度非常慢
    if n not in [1, 2]:
        return climb_2(n - 1) + climb_2(n - 2)
    else:
        return n

但是运行下来,会发现,climb_2(3) = 3 这个值早在最开始已经被计算出来了,但是后面的climb_2(4)以及后面所有的递归,都会再去重新计算一下这个取值。当嵌套复杂起来,做了非常多的重复工作,速度很差。

 

方案2:

dict_t = {1:1, 2:2}
def climb_1(n):
    global dict_t
    if n not in dict_t.keys():
        dict_t[n - 1] = climb_1(n - 1)
        dict_t[n - 2] = climb_1(n - 2)
        dict_t[n] = dict_t[n - 1] + dict_t[n - 2]
    return dict_t[n]
 

这样虽然也是递归嵌套,但是有了全局的dict_t的字典做存储,不会再傻乎乎将climb_2(3) 重新计算,算是一定层面的优化了。但是呢有个缺点:必须依赖函数外的nonlocal或者global变量。

方案3:

@functools.lru_cache(3)
def climb_3(n):
    if n not in [1, 2]:
        return climb_3(n - 1) + climb_3(n - 2)
    else:
        return n

此种方案也是最近学习functools才发现的,有个lru_cache 可以缓存函数的返回值,思路也同方案2,但是更加优雅、干练,而且也摆脱了nonlocal变量的依赖。但是这里还有个问题没弄明白。cache的大小为啥3就够快,但是2就不够。此处留个问号?

 

方案4:

def climb_4(n):
    def inner():
        (a, b) = (1, 2)
        yield a
        yield b
        while 1:
            yield (a + b)
            (a, b) = (b, a + b)
            
    bo = inner()
    for _ in range(n):
        s = next(bo)
    return s

方案4用到了yield关键词(最近才学明白),也即生成器。yield可以看成是个特殊的return,只是当再次进入这个方法时,会从上会的yield存档处继续。会有变量保存的效果。

受到这层启发后,编写了上面这个方案,n为1,n为2,设了两道关卡,yield出来个a,yield出来个b。。过了1,2后,就进入了死循环阶段。将a + b生成出去,当再次进入函数时,再将a与b重新继续赋值。整体看起来很不错的一种方案。