ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

python leetcode 斐波那契数列 动态规划 递归 算法

2021-01-28 15:34:38  阅读:208  来源: 互联网

标签:fib return 递归 python memo 斐波 那契 复杂度 def


题目链接

https://leetcode-cn.com/problems/fibonacci-number/

斐波那契数,通常用F(n)表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1 给你 n ,请计算 F(n) 。

示例 1:

输入:2 输出:1

解释:F(2) = F(1) + F(0) = 1 + 0 = 1


纯递归


递归有两个基本要素:基例以及递归关系式。

  • 基例:F(0) = 0,F(1) = 1,即递归结束的地方
  • 递归关系式:F(n) = F(n-1) + F(n- 2)

def fib(n):
   #base case
   if n <= 1:
      return n
   elif n >= 2:
      return fib(n-1) + fib(n-2)

然后根据python语法特性,可以缩写为一句话

def fib(n):
  return fib(n-1) + fib(n-2) if n >= 1 else n

优化


F(20) = F(19)+F(18)

F(19) = F(18)+F(17)

然后我们发现 F(18) 重复,即冗余结构。这样的运算时间会很大,

节点数 * 每个子问题所需要的时间 = 时间复杂度

显而易见,这种情况下为O(2^N)

纯递归的缺点就在于存在过多冗余结构。


递归加记忆


将指数级时间将为多项式级时间。

我们可以将已经出现计算好的存在一个备忘录里面,这样我们下一次计算之前,先去查一遍表就行,不需要再重新计算。

memo = {}
def fib(n):
    #查表
    if n in memo:
       return memo[n]
    #base case:
    elif n <= 1:
       return n
    else:
       ans = fib(n-1) + fib(n-2)
       memo[n] = ans
       return memo[n]

解释


当然,上述的备忘录既可以采用数组,同时也可以用哈希表。

另外,如果追求完美一点,可以将基例存储到备忘录里面,这样可以减少一定程度的空间复杂度。

memo = {0:0,1:1}

这里介绍的是由上而下的方法,当然也可以是由下而上的。虽然比纯递归好,但是时间复杂度依然很高,leetcode上为1500-1600ms左右。


bottem-up-solution


def fib(n):
    memo = {0:0,1:1}
    for i in range(2,n+1):
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]

解释


for循环的左边是因为备忘录里面已经有键0,1了,所以从2开始。然后我们最后要计算的是memo[n],i = n 所以要为n+1

优化


这个的时间复杂度会大大降低,两者看似都是查表,但是由下而上的这种不会重新迭代函数,只会不断计算表。leetcode数据为36ms

算法方面的优化到此为止,但是关于空间复杂度可以更优化一点,

import sys

h = {0:0,1:1}
l = [0,1]

print(sys.getsizeof(h))
print(sys.getsizeof(l))

#232
#72

字典的空间复杂度远大于列表,所以可以更优化一点。单方面来讲,复杂度跟元素大小无关。跟元素个数有关。

def fib(n):
    if n == 0:
       return 0
    memo[1 for i in range(n+1)]
    memo[0],memo[1] = 0,1

    for i in range(2,n+1):
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]

解释


关于第一行生成len = n+1的原因,因为最后返回索引为n,而索引从0开始

而且除了列表生成器的表达,还可以 memo = [1] * (n+1)

之所以提取生成长度为n+1的列表,是为了防止列表索引不够,进行有序的替换。

因为是替换的,所以初始化列表的值是任取的,只要保证基例正确就行。

要小心的是,如果输入0没有开头的if语句是会报错的,原因在于生成了长度为1的列表,却有memo[1] = 1这种操作,然而,列表并不像字典一样可以创建,则导致长度不够用报错

关于for 循环

for i in range(2,1)
for i in range(2,2)
#两者均不会报错且都不执行

经验之谈


还有就是力扣的很多时候判别的不准,大家只要往死里优化,看看几个最优解就行。不必苛求100%,大家可以复制几个100%,有些是运气100%,到你这就不是100%

拓展


滚动变量

def fib(n):
    if n == 0:
       return 0
    for i in range(n):
        a,b = b,a+b
    return a

这种方法显然不是最优解,但是值得一说的是,按照一些书中python的代码规范。这种结构可读性不好,建议少用。

还有一个更流氓一点的解法:

面对答案编程

class Solution:
    def fib(self, n: int) -> int:
        a = [0,1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040]
        return a[n]

细心的同学注意到n的范围为[0,30],所以生成一个长度为31的列表就行。

标签:fib,return,递归,python,memo,斐波,那契,复杂度,def
来源: https://www.cnblogs.com/sherlcok314159/p/14340023.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有