前端面试工程师

斐波那契数列传统递归思路到动态规划

2020-11-19  本文已影响0人  弱冠而不立

推荐文章:动态规划套路详解

传统思路:

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)

上一篇下一篇

猜你喜欢

热点阅读