前言
今天上班咖啡时间在逛牛客网时看到美团点评的一道算法题,原题如下
形如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.写在最后
我是前端小将廷仔,跟我一起每天前进一点点。