🎆

动态规划

动态规划是一种思维逻辑, 并没有固定的"公式"

爬楼梯问题

题目描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意: 给定 n 是一个正整数。
示例
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶
  • 1 阶 + 1 阶
  • 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
  • 1 阶 + 1 阶 + 1 阶
  • 1 阶 + 2 阶
  • 2 阶 + 1 阶

思路

1. 递归
思路
f(n) 的结果是 n 个台阶有多少种不同方法到楼顶
易知 f(1) = 1, f(2) = 2
因为只有 n-1n-2 能够到达 n
所以有 f(n) = f(n-1) + f(n-2), f(n-1) = f(n-2) + f(n-3)
代码实现
// 递归
const recursive = (n) => {
    if (n === 1) {
        return 1
    }
    if (n === 2) {
        return 2
    }
    return recursive(n - 1) + recursive(n - 2)
}
优化点
使用递归来解决这个问题, 再 OJ 中会直接超时
因为有大量的重复计算, 如下图红色点
notion image
 
2. 记忆化搜索
👉
解决时间效率上的问题, 最直接的思路就是空间换时间
💡
所谓记忆化搜索就是缓存已计算出的结果, 避免重复计算
代码实现
// 记忆化搜索
const f = []
const memorize = (n) => {
    if (n === 1) {
        return 1
    }
    if (n === 2) {
        return 2
    }
    // 判断 f(n) 是否存在, 如不存在, 则计算; 如存在, 则直接返回
    return f[n] ? f[n] : recursive(n - 1) + recursive(n - 2)
}
 
3.动态规划
代码实现
// 动态规划
const dp = (n) => {
    // 初始化状态数组
    let f = [1, 2]
    // 更新每一层楼梯对应的结果
    for (let i = 2; i < n; i++) {
        f[i] = f[i - 1] + f[i - 2]
    }
    // 返回结果
    return f[n - 1]
}

找硬币问题

💡
提示:最值问题是动态规划的常见对口题型,见到最值问题,应该想到动态规划

题目描述

给定不同面额的硬币 coins和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例
示例1: 输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例2: 输入: coins = [2], amount = 3
输出: -1

思路

 

代码实现

/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function (coins, amount) {
    // 保存每个 amount 的最小硬币个数
    const f = []
    // 已知条件
    f[0] = 0
    // 遍历 [1, amount] 之间总额
    for (let i = 1; i <= amount; i++) {
        f[i] = Infinity
        // 遍历硬币数值, 找出可以用的面值
        for (let j = 0; j < coins.length; j++) {
            // 当总额大于当前硬币面值, 才有可能是最小值
            if (i - coins[j] >= 0) {
                f[i] = Math.min(f[i], f[i - coins[j]] + 1)
            }
        }
    }

    if (f[amount] === Infinity) {
        return -1
    }
    return f[amount]
};

思考

记忆化搜索和动态规划的区别

  • 记忆化搜索可以理解为优化后的递归, 递归思想是一个自顶向下的过程
  • 动态规划, 是一个自底向上的过程, 要求我们在已知的角度, 推导出已知和未知的关系

动态规划和分治思想

分治思想: 把一个问题分解为相互独立子问题, 逐个解决子问题后, 再组合子问题的答案, 就得到了问题的最终解
  • 分治思想的各个子问题之间是独立的
  • 动态规划的思想划分的子问题是相互依赖相互影响

什么时候应该使用动态规划

  • 最优子结构
  • 重叠子问题