Day8刷算法-动态规划(1)

168 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天 本文先通过斐波那契数,比对递归法和动态规划法的不同,来初步理解动态规划。然后再加上一个零钱兑换加深思路

力扣509-斐波那契数

image.png 本例会用四种方法来实现:递归法,动态规划法,备忘录法,两数法。

1.递归法

/**
 * @param {number} n
 * @return {number}
 */
 var fib = function(n) {
    if (n === 0 || n === 1) {
        return n;
    }
    return fib(n - 1) + fib(n - 2)
};

这种解法就是根据题意,直接书写,性能很差,但是面试如果出现(几乎不太可能),这个解法大概率是个负分答案了,所以下面进行优化。

2.动态规划法(推荐)

解题思路:动态规划(dynamic programming)可以理解为递推。
我们先定义一个初始化条件,然后写递推公式,最后返回结果。

/**
 * @param {number} n
 * @return {number}
 */
 var fib = function(n) {
    let dp = [0,1]
    for (let i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2]
    }
    return dp[n]
};

3.备忘录法

思路解释:先把斐波那契数列想象成一颗树,如下图所示 image.png 该结构中存在大量重复运算,那么我们把这部分缓存起来,存在则直接使用,不存在再进行缓存,最终得到结果。

/**
 * @param {number} n
 * @return {number}
 */
 var fib = function(n) {
    let memo = []
    function helper(memo, n) {
        if (n <= 1) {
            return n;
        }
        if (memo[n]) {
            return memo[n]
        }
        memo[n] = helper(memo, n-1) + helper(memo, n-2)
        return memo[n]
    }
    return helper(memo, n)
};

4.两数法

思路解析:f(n) = f(n - 1) + f(n - 2),f(n - 1)和f(n - 2)是两个数字,可以不用数组来保存,所以我们一直模拟两个数字来存储可以提升空间复杂度。空间复杂度O(n)变为O(1)

/**
 * @param {number} n
 * @return {number}
 */
 var fib = function(n) {
    if (n <= 1) {
        return n
    }
    let dp1 = 0;
    let dp2 = 1;
    let dp3;
    for (let i = 2; i <= n; i++) {
        dp3 = dp1 + dp2;
        dp1 = dp2;
        dp2 = dp3;
    }
    return dp3;
};

力扣322-零钱兑换

image.png 本例不能用贪心算法,比如,coins=[1,2,5],amount=12,那么贪心算法不能回退,无法得到最优解。

  • 以case1为例先定义无穷大的一组数组 image.png
  • 第一次循环时,硬币为1的情况,那么每次累加1就可以完成,dp[金额-1]+1 image.png
  • 第二次循环时,硬币为2的情况,j从2开始计数(j = coins[i]),否则不够找零没有计算的意义。amount为2时,可以对硬币2找零1次,所以计数1 image.png amount为3时,可以对硬币2找零1次,但是还有对1找零1次,所以计数2 image.png amount为4时,可以对硬币2找零2次(在红框的基础上再找1次)所以计数2 image.png amount为5时,5-2=3,刚才我们在分析了3需要招零2次,那么再找零一次2就可以,所以为3;
    amount为6时,6-2=4,那么在4的基础上+1,也为3,后面以此类推 image.png
  • 第三次循环,amount从5开始找零,那么5就为1;
    amount为6时,6-5=1,那我们在1的基础上加上刚才找零的5的个数1,所以为2;
    amount为7时,7-5=2,此时2的位置也是1,所以加上找零的5的个数1,也为2;
    amount为8时,7-5=3,3的位置是2(说明3需要最少两张),所以再加上刚才的5的一张,8的位置就是3。。。 image.png 最后,3就是最小的张数
/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
 var coinChange = function(coins, amount) {
    if (!amount) {
        return 0;
    }
    let dp = Array(amount + 1).fill(Infinity);
    dp[0] = 0;
    for(let i = 0; i < coins.length; i++) {
        for (let j = coins[i]; j <= amount; j++) {
            
            dp[j] = Math.min(dp[j-coins[i]]+1, dp[j])
            console.log(dp)
        }
    }
    return dp[amount] === Infinity ? -1 : dp[amount]
};