这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战
LvLin 最近在学习动态规划相关算法,讲道简单的动态规划入门题目,希望能给你带来启发。
斐波那契数列
「斐波那契数列」大家应该都挺熟悉。该数列的第 0 个数为 0,第 1 个数为 1,之后的每一个数都为前面两个数之和,用公式表示如下:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),n > 1
很容易理解对吧,那让你写一个函数,根据参数 n,返回相应的斐波那契数,该怎么实现呢?
也很简单吧,根据上面的公式,用递归的方式实现,如下所示
var fib = function(n) {
if (n <= 1) {
return n;
}
return fib(n-1) + fib(n-2);
};
让我们深入分析一下,假如 n 为 8,那么就要先计算出 fib(7) 和 fib(6),而运行 fib(7) 的时候,又要先计算出 fib(6) 跟 fib(5),可以发现 fib(6) 被计算了两次。
用一棵树来表示一下递归的过程:
可以看到有很多重复的数字被计算。有什么办法可以优化一下吗?
假如我们把已经计算过的数字都保存起来,那是不是就可以避免重复计算了?参考代码如下所示:
var fib = function(n) {
let arr = new Array(n+1); // 不要忘了 0
arr[0] = 0;
arr[1] = 1;
function fibonacci(i) {
if (arr[i] == undefined) {
arr[i] = fibonacci(i-1) + fibonacci(i-2)
}
return arr[i];
};
return fibonacci(n);
};
看一下优化后的递归树是怎么样的:
优化效果非常明显。
再以 8 举例。既然我们可以从 8 开始自顶向下递归到 0,那么是不是也可以直接就从 0 开始,自底向上计算到 8 ? 实现的代码如下所示:
var fib = function(n) {
let arr = new Array(n+1); // 不要忘了 0
arr[0] = 0;
arr[1] = 1;
for (let i = 2; i <= n; i++) {
arr[i] = arr[i-1] + arr[i-2];
}
return arr[n];
};
学会了吗?可以在 LeetCode 上试试看能不能通过这道题。
看到这的时候,你就已经掌握了最基本的动态规划算法了。所谓动态规划,其实就是通过记住已经求出的解,并在当前解的基础之上求解下一步的思想。
我们刚刚用一个数组,记录了已经求得的解,这个数组叫做 DP table,也叫「备忘录」。通过arr[i] = arr[i-1] + arr[i-2]不断求得下一个解,这就是在当前解的基础上求解下一步的方法。
关于这道题,还有更进一步的优化空间。可以想一下怎么样能对空间做进一步的优化?
相关代码如下所示:
var fib = function(n) {
if (n < 2) return n;
let a = 0, b = 0, c = 1;
for (let i = 2; i <= n; i++) {
a = b;
b = c;
c = a+b;
}
return c;
};
看懂了?再接着试试这道题?
如果文章给你带来了思考,给 LvLin 点个赞呗~