斐波拉契数列及其应用 (递归与循环)

相信大家一定不陌生斐波那契数列吧,今天小G就和大伙儿分享一下斐波拉契数列背后隐藏的递归和循环思想。

先简单描述一下,什么是斐波拉契数列呢?

斐波那契数列又称黄金分割数列。数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”。它指的是这样一个数列:0、1、1、2、3、5、8、13、21、……在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*),用文字来说,就是斐波那契数列列由 0 和 1 开始,之后的斐波那契数列系数就由之前的两数相加。特别指出:0不是第一项,而是第零项。现在我们想要编程实现这样一个数列,想想该怎么办?请你输出斐波那契数列的第n项(从0开始,第0项为0)。 

这个问题可以说是迭代(Iteration)VS 递归(Recursion),f(n) = f(n-1) + f(n-2),第一眼看就是递归啊,简直完美的递归环境!!递归肯定很easy!!这样想着,小G兴奋的敲下了关键的两三行代码,如下:

class Solution:
    def Fibonacci(self, n):
        if(n<=1): return n;
        else: return self.Fibonacci(n-1)+self.Fibonacci(n-2)

Hold on!Hold on!! Hold on!!!

哗哗地,小G的脑袋里突然响起了一个冰冷的声音:如果给你一个超大数值的n怎么办呢,比如说n=10000000000。小G看了一眼刚刚敲好的程序,陷入了沉思:原来自己的程序里存在着大量的重复计算呀,而且重复的情况还很严重!举个小点的例子,n=4,看看程序怎么跑的:

Fibonacci(4) = Fibonacci(3) + Fibonacci(2);

                    = Fibonacci(2) + Fibonacci(1) + Fibonacci(1) + Fibonacci(0);

                    = Fibonacci(1) + Fibonacci(0) + Fibonacci(1) + Fibonacci(1) + Fibonacci(0);

由于我们的代码并没有记录Fibonacci(1)和Fibonacci(0)的结果,对于程序来说它每次递归都是未知的,因此光是n=4时f(1)就重复计算了3次之多。那么如何求解呢,动态规划似乎不错,以一定的空间代价避免代价更大的重复计算的栈空间浪费,关于动态规划三个条件:最优子结构、无后效性、子问题重叠这些就不谈了,因为理(wo)论(ye)性(bu)太(tai)强(dong)了。

想到这里,小G如释重负,搓搓双手敲下了下面的代码:

class Solution:
    def Fibonacci(self, n):
        l = [0,1]
        while len(l)<=n:
            l.append(l[-1]+l[-2])
        return l[n]

 取一个超级大的n,看一下运行的时间和占用的内存 :

785b8d10b4f275919ebf9a22a81cc418511.jpg

嗯!效果还不错啊!!

其实斐波拉契数列这种想法有很多奇奇怪怪的变式花样,比如前一阵子小G看到一个关于跳台阶的问题。描述如下:

有一只可爱的小青蛙,闲着无聊想要锻炼身体,于是找了个废弃的郊外别墅练习“蛙跳”。由于体力有限,它一次只能跳上1级台阶,也可以发个力跳上2级。那么请你想一想,这只小青蛙跳上一个n级的台阶总共有多少种跳法呢?

我们可以把这个问题分解一下:

a. 如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);

b. 假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)

c. 由a\b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2) 

d. 然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2

e. 可以发现最终得出的是一个斐波那契数列!

注意:因为小青蛙每次都不能原地跳,所以初值记得调整一下哦。简单实现一下如下:

class Solution:
    def jumpFloor(self, n):
        l = [1,1]
        while len(l)<=n:
            l.append(l[-1]+l[-2])
        return l[n]

看到小青蛙在这不停的蹦跶,一只路过的蛙界国家队运动员停下了脚步,对小青蛙说:“你这跳的也忒慢了吧。我可以一次性就跳上n级台阶,但我也可以每次偷个懒只跳1级台阶,或者跳上2级,3级,.....,n-1级等等。那么请你想一想,这个青蛙运动员跳上一个n级的台阶总共有多少种跳法呢?

来理一下思路吧。

因为n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级;

跳1级,剩下n-1级,则剩下跳法是f(n-1);

跳2级,剩下n-2级,则剩下跳法是f(n-2);
所以f(n)=f(n-1)+f(n-2)+...+f(1)
因为f(n-1)=f(n-2)+f(n-3)+...+f(1)

所以f(n)=2*f(n-1)

直接看图:

d529a44d28b27a8e84358e4a21093d7a969.jpg

代码实现如下:

class Solution:
    def jumpFloorII(self, n):
        if n <=1: return n
        else:
            i, temp = 1, 1
            while i < n:
                l = 2*temp
                temp = l 
                i += 1
            return l

其实上面的解法有些繁琐了,小G是是一个追求简约的人。敲这么多行代码太折腾嘞。。。。

我们来换一个思路。

我们可以把每个台阶看作一块木板,让青蛙运动员跳上去,n个台阶就有n块木板,最后一块木板是青蛙到达的位子, 必须存在,其他 (n-1) 块木板可以任意选择是否存在,则每个木板有存在和不存在两种选择,(n-1) 块木板 就有 2^(n-1) 种跳法,可以直接得到结果。 2^(n-1)可以用位移操作进行,更快。

哈哈,这个想法是不是很清新脱俗呢?那么来实现一下吧。

class Solution:
    def jumpFloorII(self, n):
        return  1<<(n-1)

可能有些朋友还见过这样一类矩形覆盖问题。用2*1的小矩形横着或者竖着去覆盖更大的矩形,问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 

其实这种问题说到底依旧是斐波那契数列,只是在初始值时不太一样。我们来分析一下:

6e43775468b197791ae4a7a7ac23254a401.jpg

代码实现一下吧:

class Solution:
    def rectCover(self, n):
        l =[0,1,2]
        while len(l) <= n:
            l.append(l[-1]+l[-2])
        return l[n]

好了,今天的斐波拉契数列问题到此就要结束啦。最后来总结一下吧。

对待这类递归迭代问题,我们需要首先分析一下问题的初始情况。对于初始情况进行特殊分析,不怕麻烦的话,可以动手提笔写一写,总结一下是否存在着隐含的规律。递归问题说到底,就是把未来的解划归为以前解的组合。但是实现的话,如果只是简单机械的用递归来实现,必然会存在大量的重复计算,从而造成极大的运行时间开销。这时候最的方法就是采用迭代+循环。

转载于:https://my.oschina.net/magicalee/blog/2120549

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值