细说斐波那契数列的四种代码实现

关于斐波那契数列的背景相信大家都有所耳闻,不知道的可以去搜索一下兔子问题。在大多数的应用场合,没有人会直接让你编写一段代码实现斐波那契数列,而会把他装在一个应用场景中,这个场景可以是兔子生兔子,也可以是青蛙爬楼梯,比起学会如何实现它,大家更需要掌握的技能是,看到应用场景可以反映出这实际上是一个斐波那契问题。毕竟如何实现斐波那契,网上的教程一搜一堆,但判断当前问题可不可以用斐波那契思想解决,需要你的判断才行。

言归正传,该问题的四种代码实现分别为 递归法、迭代法、通项公式法和矩阵法,后两种方法对于线性代数基础薄弱的人来说可能不太好理解,但是学会了它受益良多,还是建议大家能够掌握。

一、递归法

递归解决斐波那契问题虽然很直观,但存在的问题是时间复杂度太高(重复计算太多),所以给出代码各位看看就好。

class Solution {
    public int climbStairs(int n) {
        if(n == 0) return 0;
        else if(n == 1) return 1;
        else return climbStairs(n - 1) + climbStairs(n - 2);
    }

二、迭代法

这里利用了动态规划的思想,从公式可以看出,当前的状态等于前一时刻的状态+前两时刻的状态,我们知道了第0时刻和第1时刻,递推就可以得到结果啦,显然时间复杂度为o(n)。

class Solution {
    public int climbStairs(int n) {
        int fibNOne = 1, fibNTwo = 0, fibN = 0;
        for (int i = 1; i <= n; ++i) {
            fibN = fibNOne + fibNTwo;
            fibNTwo = fibNOne; //前两时刻
            fibNOne = fibN; //前一时刻
        }
        return fibN;
    }
}

三、通项公式

其实递归法已经足够好了,但是精益求精的我们会琢磨有没有时间复杂度更快地方法呢,还真有,o(logn)。

我们首先需要了解齐次线性递推公式的特征方程:

对于形如:f(n)=c_{1}\ast f(n-1)+c_{2}\ast f(n-2) 的递推式,可得结论:

1)其递推方程为 x^{2}- c_{1}x-c_{2}=0

2)f(n)=\left\{\begin{matrix} A\ast x_{1}^{n}+B\ast x_{2}^{n} &x_{1}\neq x_{2} \\ (A+B\ast n)\ast x_{1}^{n}& x_{1}= x_{2} \end{matrix}\right.

故而由斐波那契数列可得齐次线性方程x^{2}- x-1=0,解方程可得 x1=\frac{1+\sqrt{5}}{2}x2=\frac{1-\sqrt{5}}{2}

由 f(0)=0f(1)=1,可得f(n)=\frac{1}{\sqrt{5}}\left [ \left (\frac{1+\sqrt{5}}{2} \right )^{n}-\left (\frac{1-\sqrt{5}}{2} \right )^{n} \right ]
此时f(n)直接可求。

class Solution {
    public int climbStairs(int n) {
        return (int)((Math.pow((1+Math.sqrt(5))/2,n)-Math.pow((1-Math.sqrt(5))/2,n))/Math.sqrt(5));
    }
}

这段代码的实现之所以是o(logn)原因就在于Math.pow这个函数,下面我们讲解一下这个函数的实现原理:

pow【快速幂算法】本质上是一个分治算法,故而时间复杂度是o(logn)。我们求x^{n}实际上是在前一个结果的基础上进行平方操作。我们找到了问题的重复子问题,自然而然可以想到递归操作。有某些时刻,平方后的结果还需再乘上一个x。判断是否需要额外乘x的方法很简单,就是当n为奇数时,结果的平方需再乘以一个x。具体实现如下:

class Solution {
    public double myPow(double x, int n) {
        return n<0 ? 1/dfs(x, n) : dfs(x,n);
    }

    public double dfs(double x, int n) {
        if(n==0) return 1.0;
        double y = dfs(x,n/2);
        return n%2==0 ? y*y : y*y*x;
    }
}

另一个比较巧妙地思路是利用二进制位算法,我们利用规律:

n=2^{i_{0}}+2^{i_{1}}+...+2^{i_{k}} 则 x^{n}=x^{2^{i_{0}}}*x^{2^{i_{1}}}*...*x^{2^{i_{k}}}

具体实现如下:

lass Solution {
    public double pow(double x, long n) {
        double res = 1.0;
        double x_contribution = x;
        while(n>0) {
            if((n&1)==1) res*=x_contribution;
            x_contribution *= x_contribution;
            n=n>>1;
        }
        return res;
    }
    public double myPow(double x, int n) {
        long b = n; //防止执行n=-n时数组越界
        return b<0 ? 1.0/pow(x,-b) : pow(x,b);
    }
}

四、矩阵快速幂

第三种方法中的根号和除法会存在计算精度的问题。受到快速幂算法的启发,如果可以把求解过程转化为求解n次幂,那问题便可迎刃而解。这就要用到线性代数的知识了。

我们知道对于斐波那契数列,状态是这样转移的:

\left [ a \right b ] \rightarrow \left [ b \right a+b ]

填0变成矩阵,则可得方程:

\begin{bmatrix} a & b \\ 0 & 0 \end{bmatrix}* \begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix} = \begin{bmatrix} b & a+b \\ 0 & 0 \end{bmatrix}

推导出:

\begin{bmatrix} f(0) & f(1) \\ 0 & 0 \end{bmatrix}* \begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix}^{n} = \begin{bmatrix} f(n-1) & f(n) \\ 0 & 0 \end{bmatrix}

此时求f(n)的问题便转化成了一个求矩阵n次幂的问题:

class Solution { 
    public int climbStairs(int n) {
        int[][] q = {{0,1},{1,1}};
        int[][] res = myPow(q,n);
        return res[1][0];
    }
    
    public int[][] myPow(int[][] q, int n) {
        int[][] res = {{1,0},{0,1}};
        int[][] x_contribution = q;
        if(n==0) return new int[2][2];
        while(n>0) {
            if((n&1)==1) res = multiple(res, x_contribution);
            n = n>>1;
            x_contribution = multiple(x_contribution,x_contribution);
        }
        return res;
    }

    public int[][] multiple(int[][] a, int[][] b) {
        int[][] c = new int[2][2];
        for(int i=0;i<2;i++) {
            for(int j=0;j<2;j++) c[i][j] = a[i][0]*b[0][j]+a[i][1]*b[1][j];
        }
        return c;
    }    
}

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值