推荐文章:动态规划套路详解
传统思路:
fib(0) = 0; fib(1) = 1; fib(n) = fib(n-1) + fib(n-2)
这是每个算法课老师,讲递归的经典例题。但是leetcode上其实是把这个当作递归的反面教材的。
原因就在于(举个例子):当计算 fib(8) 时,你要计算 f(7) 和 f(6),你要计算 f(7) 时,你又要计算f(6) 和 f(5),这就意味着存在大量的重复计算。
传统思路 + 备忘录
这里备忘录的意思就是,开辟一个数组,把每次计算的结果都存进去,如果这个备忘录数组中有了,则直接拿出来。举个例子:当计算好 f(6) 时,把 f(6) 存到 memory[6] 中,然后当计算 f(7) 时,就把备忘录里的 memory[6] 拿出来
var fib = function(n, memory=[]) {
// 递归出口
if(n < 2) {
return n;
}
//如果以前没计算过就把计算的推到备忘录
if(!memory[n]) {
memory[n] = fib(n-1, memory) + fib(n-2, memory)
}
// 否则备忘录里有数据直接返回备忘录里的数据
return memory[n];
}
备忘录为主体的循环迭代
根据上面的备忘录思想,我们可以把备忘录单独抽离出来,即 memory[i] 是依赖 memory[i-1] + memory[i-2]
var fib = function(n) {
let dp = []
dp[0] = 0;
dp[1] = dp[2] = 1
for(let i=3; i<=n; i++) {
dp[i] = dp[i-1] + dp[i-2]
}
return dp[n]
};
动态规划思路
斐波那契的状态转移方程上面几种解法的核心思想都是这个状态转移方程:如 fib(n) = fib(n-1) + fib(n-2);dp[i] = dp[i-1] + dp[i-2]。但是其实我们不需要存储那么多状态,只需要存储前两个状态就可以了。根据这个思路,我们可以将空间复杂度降到O(1)
var fib = function(n) {
if(n<2) {
return n;
}
// 相当于 fib(0)
let preVal = 0;
// 相当于 fib(1)
let curVal = 1;
for(let i=2; i<=n; i++) {
// 相当于 fib(2) = fib(1) + fib(0)
let sum = curVal + preVal;
// 把preVal设为fib(1)
preVal = curVal;
// 把curVal设为fib(2)
curVal = sum;
// 即下次循环的时候 sum = fib(1) + fib(2), 此时sum就相当于 fib(3)了
}
return curVal;
};
这样一步步下来,现在我们解斐波那契数列的算法的时间复杂度为 O(n),空间复杂度为 O(1)
网友评论