0.斐波那契数列定义
常见的问题有爬楼梯问题:有n级楼梯,每次可以爬1级或者2级,问爬上n级有多少种爬法?这个问题与斐波那契数列只是初始条件不一样。
1.递归法
function fib1(n) {
if (n === 1 || n === 2) {
return 1;
}
return fib1(n - 1) + fib1(n - 2)
}
递归法简单易懂,只是时间复杂太高。 比如求。
从上面三个式子中发现被计算了两次,当n较大时,会有更多的子项被多次计算,造成了计算冗余,时间复杂度高达。
2.备忘录递归法
既然发现被重复计算了,那么我们用额外的空间将其结果进行存储,如果没有被计算过,则进行计算,并将其存储,下次碰到的时候直接从存储空间中获取,这样会节省很多时间。
function fib2(n) {
let memory = new Array(n + 1).fill(0);
const memo = (memory, k) => {
if (k === 1 || k === 2) return 1;
if (memory[k]) return memory[k];
memory[k] = memo(memory, k - 1) + memo(memory, k - 2);
return memory[k];
};
return memo(memory, n);
}
时间复杂度,空间复杂度。
3.自底向上备忘录法
递归法在计算时,都是从大的数进行计算,直到,既然可以从大到小,那么也可以从小到大来填充备忘数组。
function fib3(n) {
let memory = new Array(n + 1).fill(1);
for (let i = 2; i < n; i++) {
memory[i] = memory[i - 1] + memory[i - 2];
}
return memory[n - 1];
}
这里,所以。时间复杂度,空间复杂度。
4.丢掉备忘录吧
其实无需额外的空间,用变量记住前两个值,当前值等于前两个值相加,然后再更新前两个值,这样可将空间复杂度下降到。
function fib4(n) {
if(n === 1 || n === 2) return 1;
let pre = 1, cur = 1, sum = 2;
for(let i = 3; i <= n; i++) {
sum = pre + cur;
pre = cur;
cur = sum
}
return cur;
}
5.通项公式法
梦回高中吧!(如果你正在读高中,当我没说)
function fib5(n) {
let sqrt5 = Math.sqrt(5);
return (Math.pow(1 + sqrt5, n) - Math.pow(1 - sqrt5, n)) / (sqrt5 * Math.pow(2, n));
}
由于精度问题可能会算出小数,这里暂不处理。
6.耗时测试
主要比较fib4与fib5的耗时,因为fib5虽然时间复杂度是,但是也是需要计算的。 测试代码:
const test = [
1000000, 10000000, 100000000, 1000000000
];
for (let i = 0; i < test.length; i++) {
let n = test[i];
t1 = new Date().getTime();
fib4(n);
t2 = new Date().getTime();
fib5(n);
t3 = new Date().getTime();
console.log("fib4", t2 - t1);
console.log("fib5", t3 - t2);
}
测试结果截图
1000000 | 10000000 | 10000000 | 1000000000 | |
---|---|---|---|---|
fib4 | 19 | 68 | 172 | 1607 |
fib5 | 0 | 0 | 0 | 0 |
由此可见,确实是的牛逼些!