斐波那契数
指的是这样一个数列:0、1、1、2、3、5、8、13、21、……
用数学公式表示为:
简单的来说就是 后一项是前两项之和。
方法一:递归
时间复杂度:O(n^2),空间复杂度:O(n)
// 递归法
function Fibonacci(n) {
if (n < 2) {
return n;
} else {
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
由于是递归调用,每次调用F函数的时候,会导致F(n)重复计算。因为,每个值最终被拆解为 F(1)+F(0).
正如上图,F(5)= F(1)+F(0)+F(1)+F(0)+F(1)+F(1)+F(0)+F(1) = 8
方法二:尾递归
时间复杂度为O(n)
定义:当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。
简单理解,就是处于函数尾部的递归调用本身的情形下,前面的变量状态都不需要再保存了,可以释放,从而节省很大的内存空间。
递归和尾递归写法的区别
return f(n-1)+f(n-2) //递归
return f(n-1,...) //尾递归
//尾递归 不爆栈
function Fibonacci(n, a, b) {
if (n <= 1) {
return a;
}
return Fibonacci(n-1, b, a+b)
//每次的b就是要求当前的返回值,当执行到b减到0的时候,此时的b就是我们要求的第n个数
}
尾递归的调用:F(5,1,1)=F(4,1,2)=F(3,2,3)=F(2,3,5)=F(1,5,8)=F(0,8,13)
所以,当我们调用F(5,1,1)的时候相当于变相的调用了F(0,8,13),正如上文中所说 :当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。 因为后续的方法并不依赖于之前的方法。
方法三:迭代(使用变量)
使用三个变量存储可以节省内存,但缺点是只能保存最后计算的值以及前两个值,以前的值会被替换掉。
时间复杂度为O(n),空间复杂度为O(1)
//动态规划
function Fibonacci2(n) {
if (n <= 1) {
return n;
}
var n1 = 0, n2 = 1, sum;
for (var i = 2; i < n; i++) {
sum = n1 + n2
n1 = n2
n2 = sum
}
return sum
}
斐波那契数列的应用题:青蛙跳台阶问题、变态跳台阶、矩形覆盖