前端算法——斐波那契数列

691 阅读3分钟

前言

今天上班咖啡时间在逛牛客网时看到美团点评的一道算法题,原题如下

形如1, 1, 2, 3, 5, 8, 13, 21, 34, 55的数列,后一位是前面两位相加(斐波那契数列),写出函数要求找到第 N 位是多少,如:fib(3) => 3 , fib(5) => 8, 要求时间复杂度为O(n)。

1.常规思路

遇到这种类似问题,我首先想到的是循环,这也是比较常规的一种思路。这道题的核心就是 arr[index] = arr[index-1]+arr[index-2],代码如下

let fib = funtion (num){
  let arr = [];
  if(!!num && num >= 0){
      for(let i=0; i<= num; i++){
          arr[i] = i >1 ? arr[i-1]+arr[i-2] : 1;
      }
      return arr[num]
  }else {
      return 'Impossible';
  }
}
// fib(3) =>3

2.递归思路

查阅资料发现大佬们对这个实现的方式有很多种,其中递归的方法最为优雅,但理解上会更抽象一点,于是又梳理了一下思路,学习如何更优雅的实现。

  • 1.假设函数fib(index) => num,第index位就是num
  • 2.后一位是前面两位相加,即 fib(index) = fib(index-1) + fib(index-2)
  • 3.index<=1时num值都为1

我们把上述逻辑用代码表达:

let fib = function(num){
   return  num <= 1 ? 1 : fib(num-1)+fib(num-2)
}
// fib(5) => 8

经测试发现当index值比较大的时候第一种方法正常,第二种打印不出来返回值,排查发现原来是递归调用的次数过多,导致栈溢出,运算异常。

3.递归优化

3.1 理解栈溢出

可能有人疑问为什么会栈溢出?假如 let fn = function(){},每当fn调用一次就会产生一层栈帧(调用记录,存储着函数的相关信息),当有函数返回,栈就会减一层栈帧,对应的调用记录才会消失,由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

方法二中return语句含有表达式导致每次调用产生新的栈帧,调用次数过多,导致栈溢出。

3.2 尾递归

函数返回的时候,调用自身本身,并且return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

简单说就是:永远只有一个调用记录,调用函数产生一个调用记录,最后一步操作return把当前函数的计算结果当做参数传递给了下一个自身调用,这样第一个函数调用产生的调用记录就消失了,因为它执行完了。依次类推,就不会溢出。

let fib = function(num,num1=1,num2=1){
   return  num <= 1 ? num2 : fib(num-1,num2,num1+num2)
}
// fib(5) => 8

4.写在最后

我是前端小将廷仔,跟我一起每天前进一点点。