[每日一题] - 斐波那契数列

503 阅读3分钟

目录

  1. 斐波那契数列
  2. 递归法
  3. 尾调用优化
  4. 高级函数
  5. 通过map记忆优化
  6. 动态规划
  7. 循环

一、斐波那契数列

首先,斐波那契数列从第0个开始,分别是

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……

因此要根据该规则,返回第n个斐波那契数

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

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

示例 2:

输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

输入:4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

二、递归法

  • 时间复杂度: O(2^n)

上次写斐波那契数列已经是18年的事情了,时间过的很快,一直忙于业务,算法是时候深入了

/**
 * @param {number} n
 * @return {number}
 */
var fib = function(n) {
    if(n === 1 || n === 0 ) return n;
    return fib(n-1) + fib(n-2);
};

递归的思路很简单,即不断调用自身方法,直到n为1或0之后,开始一层层返回数据。

使用递归计算大数字时,性能会特别低,原因有以下2点:

① 在递归过程中,每创建一个新函数,解释器都会创建一个新的函数栈帧,并且压在当前函数的栈帧上,这就形成了调用栈。因而,当递归层数过大之后,就可能造成调用栈占用内存过大或者溢出。 ② 递归造成了大量的重复计算。

递归的以上两种缺点,我们可以使用尾调用优化递推法来解决。

三、尾调用优化

尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。WikiPad[1] 用代码来说,就是B函数的返回值被A函数返回了。\

1) 举例子

function B() {
    return 1;
}
function A() {
    return B();  // return 1
}

2) 斐波那契数列

var fib = function(n, current, next) {
    if(n === 1) return next;
    if(n === 0) return 0;
    return fib(n - 1, next, current + next);
};
fib(7, 0, 1); // 13

四、高级函数

var fib = function(n) {
  let seed = 1;
	return [...Array(n)].reduce(p => {
		const temp = p + seed; 
		seed = p;
		return temp;
	},0)
};
fib(6);

记忆 来优化,斐波那契数列

优化添加了 记忆 , 也就是缓存了之前计算过的数列的值,再次计算的时候直接返回值即可。

const map = new Map()

var fib = function(n) {
    if(n < 2) return n

    if(!map.has(n)) {
        map.set(n, fib(n - 1) + fib(n - 2))
    }

    return map.get(n)
}
fib(6); // 8

六 动态规划

var fib = function(n) {
    let dp = [0, 1]
    for(let i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2]
    }
    console.log(dp)
    return dp[n]
};

// fib(6);//8
// [0, 1, 1, 2, 3, 5, 8]

七 循环

-   时间复杂度: O(n)
-   空间复杂度: O(n)

```js
function fib (n) {
  let step = []
  step[1] = 1
  if ( n >= 2) {
      step[2] = 2
  }
  if ( n <= 2) {
      return n
  }
  for ( let i = 3; i <= n; i++) {
      step[i] =  step[i - 1] + step[i - 2]
  }
  return step[n-1]
};
fib(6); // 8

参考

总结

  • 斐波那契数数的值就是它后两位的值的和,学会使用 记忆 来优化代码
  • 求出f(n),只需要知道几个更小的f(c)。我们将求解f(c)称作求解f(n)的“子问题”, 这就是DP(动态规划,dynamic programming).将一个问题拆成几个子问题,分别求解这些子问题,即可推断出大问题的解
  • 斐波那契数列 既可通过递归实现,又可通过循环实现