算法之用动态规划实现斐波那契数列

217 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

关于算法系列已更新部分文章,后续会陆续增加内容:

【1】 算法之用动态规划实现斐波那契数列

【2】 算法之双指针法的应用

【3】 算法之链表的含义

动态规划 Dynamic Programming,简称DP。它是求解决策过程最优化的一种数学方法。

动态规划

image.png

在数据结构中,动态规划可以解决很多问题,比如经典的01背包,完全背包问题等等,通过动态规划来减少重复计算。

动态规划经常容易和贪心算法混淆。动态规划是会将整个问题拆分成多个子问题,下一个状态是由前一个状态推导而来。 而贪心是局部最优来达到全局最优,与之前的状态无关。

用递归方法解决问题,可以分为以下几步:

  1. 定义一个数组dp[],用来保存过程中产生的结果
  2. 确定边界
  3. 确定初始化数据
  4. 确定状态转移方程

其中动态转移方程是这个解题的关键,往往由题目中的信息推导而来,比如斐波那契数列的规律就是某个位置元素的值等于前两个元素之和,把这个规律代入dp[]数组,列出的公式就是状态转移方程了。

斐波那契数列的实现

斐波那契数列:该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。

也就是: 0 1 1 2 3 5 8 13 21 ...


样例:

输入:n = 2

输出:1

过程:F(2) = F(1) + F(0) = 1 + 0 = 1


输入:n = 4

输出:3

过程:F(4) = F(3) + F(2) = 2 + 1 = 3


方案:一个斐波那契数列可以使用不同的方式实现:

  • 动态规划
  • 递归

下面我们先使用动态规划的方式来实现一个斐波那契数列

1. 动态规划解法

利用上面总结的几个步骤来实现一个动态规划版本的斐波那契。

var fib = function(n) {
    let dp = [];
    dp[0] = 0; 
    dp[1] = 1;
    if (n <=1) return n;
    for(let i=2;i<=n;i++) {
        dp[i] = dp[i-1] + dp[i-2];
    }
    return dp[n]
};

时间复杂度:O(n)

空间复杂度:O(n)

除此之外还有另外一种方式,可以降低空间复杂度到O(1):

因为当前结果只依赖前两个元素之和,所以只要两个变量代替dp[],复杂度自然下降。

var fib = function(n) {
    let a = 1
    let b = 0
    let temp
    if (n < 2) return n
    for(let i = 2; i <= n; i++) {
        temp = a
        a = a + b
        b = temp
    }
    return a
};

2. 递归方法

递归也是实现斐波那契数列的比较好的方式,不需要使用数组就可以获取最终结果。递归中n取值为0和1的情况作为递归终止的条件。

var fib = function(n) {
    if (n<=1) return n;
    return fib(n-1) + fib(n-2);
}

时间复杂度:O(2^n)

空间复杂度:O(n)

递归的核心是自己调用自己,并把当前过程产生的结果作为返回值