近日在极客时间上看到winter老师讲解斐波那契数列的解法,跟着把代码实现了一遍,分享在这里
斐波那契数列是指每一项均为前两项之和的数列,标准的斐波那契数列为0,1,1,2,3,5,8,13,21,34,55,89,144,233 ...
斐波那契数列问题有多种表述形式(马甲),常见的有以下几种
- 兔子繁殖问题:农场里起初有一对兔子,每年繁殖一对小兔,小兔一年后成年,问n年后农场里一共有多少对兔子?
- 跳台阶问题:小和尚从山脚下往山上跳台阶,每次可以跳一级或者两级台阶,问要跳到n级台阶,一共有多少种跳法?
- 矩形覆盖问题:用n个2*1的小矩形无重叠地完全覆盖一个2*n的大矩形,总共有多少种方法?
下面就来由简入深,一一讲解每种解法的实现及复杂度
解法一:递归实现
/**
这种方式代码看起来很简洁优美,但实际上却是效率最低,复杂度最高的方法,包含了许多的重复计算,代码复杂度是O(2^n)
理论上计算斐波那契数列第n项只需要n次运算即可得到,于是我们有了下面的第二种解法
解法二:迭代实现
/**
到这里,我们去除了不必要的重复计算,将代码复杂度减少到了O(n)
单纯从代码层面似乎没有太多可以优化的空间了,接下来让我们转变思维,从数学角度来思考一下
解法三:通项公式实现
其实前人已经为我们铺好了路,斐波那契数列已经被证实存在以下通项公式(具体推导过程还是挺复杂的,在这里不做介绍了,有兴趣的同学自己上网查阅相关资料)
/**
这里用到的Math.pow(),求N次幂的方法,可以通过计算平方来逼近N次幂,因此复杂度是O(log(n))
Math.pow()方法在JDK中是native方法实现的,我们可以自己来实现一版,原理就是将n转化为二进制,对于为1的位数,乘上x对应的次方
/**
注意,受限于浮点数的精度问题,pow的结果需要做一个Math.round()操作使其变为整数
以上的通项公式虽然降低了算法复杂度,但同时引入了浮点数这种相对耗性能的运算,另外还需要处理精度的问题,有没有其他的解法能达到同样的复杂度而不需要用到浮点数呢?于是我们把目光投向了线性代数的知识
解法四:矩阵实现
回到第二步的迭代实现,其实每一次迭代都是在做将a,b转化为b,a+b的计算,如果我们将其转化为矩阵,就有了以下公式
将斐波那契数列代入这个公式,其最初的两项为a=0,b=1,我们可以得到
所以我们先定义一个2*2矩阵的乘法函数
/**
再用该矩阵乘法来实现矩阵n次幂函数
/**
最后我们就可以实现矩阵版本的迭代计算了,复杂度同样为O(log(n))
/**
以上就是斐波那契数列常用的解法,后两种代码复杂度同为O(log(n)),矩阵解法在代码实现上要麻烦些,可读性略低,具体使用看各位的喜好
最后的思考题:
由于斐波那契数列的值会很大,n达到90左右long类型就会溢出,要计算大值只能引入BigInteger,BigDecimal这些相对耗性能的类型,那么矩阵实现的方式可能会因为运算较多而性能有所损耗,有兴趣的同学可以实测一把