跟着卡哥学算法Day 32:动态规划理论 & part1

2136 字
11 分钟
跟着卡哥学算法Day 32:动态规划理论 & part1

动态规划#

概念#

动态规划(Dynamic Programming),因此常用 DP 指代动态规划。如果某一问题有很多重叠子问题,那么使用动态规划是最有效的。

通过将问题分解为相互重叠的子问题,并利用子问题的解来高效求解原问题(利用历史记录,来避免我们重复的计算)

  • 状态定义

    • 用数组表示问题的某个阶段或子问题的解,一定要明确 dp 数组dp[i][j] 代表的含义
    • 如在背包问题中,dp[i][j] 表示前 i 个物品放入容量为 j 的背包的最大价值
  • 确定状态转移方程

    • 明确如何从子问题的解推导出当前问题的解,即找出数组间关系式,最难最重要的一步
    • 通俗讲就是找出 dp[i] 与 dp[i-1]、dp[i-2]之间的关系
    • 如斐波那契数列的转移方程:dp[i] = dp[i-1] + dp[i-2]
  • 初始化边界条件

    • 确定初始状态的值,dp 数组的第 n 项结果由状态转移方程求解得到,所以需要知道第 n-1 项,第 n-2 项…第 1 项的值
    • 初始化 dp 数组的值,如斐波那契数列中 dp[0] = 0, dp[1] = 1

解题步骤#

动规五部曲

  1. 确定 dp 数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp 数组如何初始化
  4. 确定遍历顺序
  5. 举例推导 dp 数组

为何先确定递推公式,在初始化 dp 数组呢?

因为一些情况的递推公式决定了 dp 数组要如何初始化!

动态规划如何 debug#

找问题最好的方式就是把 dp 数组打印出来,看看是不是和我们推导的公式一致。

做动规题目前,一定要把状态转移在 dp 数组上的具体情况模拟一遍,确定最后推出的是想要的结果。

509. 斐波那契数 🌟#

力扣链接 🌟

题目描述#

斐波那契数,通常用  F(n) 表示,形成的序列称为 斐波那契数列 。该数列由  0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1 给你 n ,请计算 F(n) 。

示例 1:

  • 输入:2
  • 输出:1
  • 解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

  • 输入:3
  • 输出:2
  • 解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

  • 输入:4
  • 输出:3
  • 解释:F(4) = F(3) + F(2) = 2 + 1 = 3

提示:

  • 0 <= n <= 30

解题思路#

动规五部曲

  1. 确定 dp 数组以及下标的含义

    dp[i] 代表第 i 个斐波那契数的值

  2. 确定递推公式

    斐波那契很简单:dp[i] = dp[i-1] + dp[i-2]

  3. dp 数组初始化

    dp[0] = 0, dp[1] = 1

  4. 确定遍历顺序

    根据递推公式,可以看出 dp[i]依赖 dp[i - 1]和 dp[i - 2],所以从前往后遍历

  5. 举例推导 dp 数组

    按照递推公式,我们推导下 n=10 的时候,dp 数组应该是这个数列:[0,1,1,2,3,5,8,13,21,34,55]

    写代码时打印 dp 数组,看和我们推导的数组是否一致

var fib = function (n) {
const dp = [0, 1]
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]
}
console.log(dp)
return dp[n]
}

70. 爬楼梯 🌟#

力扣链接 🌟

题目描述#

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

  • 输入: 2
  • 输出: 2
  • 解释: 有两种方法可以爬到楼顶。
    • 1 阶 + 1 阶
    • 2 阶

示例 2:

  • 输入: 3
  • 输出: 3
  • 解释: 有三种方法可以爬到楼顶。
    • 1 阶 + 1 阶 + 1 阶
    • 1 阶 + 2 阶
    • 2 阶 + 1 阶

解题思路#

注意此处是求到达第 i 层的方法数,而不是具体的步数或花费

先确定条件,每次只能爬一层或两层楼梯,那么

  • 第一层 1 种方法:1
  • 第二层 2 种方法:1+12

爬到第三层楼梯时,只有两种方法:

  1. 爬到第一层,再爬两层到第三层:1+1+11+2
  2. 爬到第二层,直接一层到第三层:2+1
  3. 那么总方法数 = 2 + 1 = 3

爬到第四层楼梯时,只有两种方法:

  1. 爬到第二层,再爬两层到第四层:1+1+22+2
  2. 爬到第三层,直接一层到第四层:1+1+1+11+2+12+1+1
  3. 总方法数:2 + 3 = 5

因此,爬到第 i 层楼梯时,也只有两种方法:

  1. 爬到第 i-2 层,再爬两层到第 i 层:i-2 + 2
  2. 爬到第 i-1 层,直接一层到第 i 层:i-1 + 1
  3. 总方法数:dp(i - 2) + dp(i - 1)

因为每一步可以选择爬 1 或 2 层,所以到达第 i 层的方法数等于到达第 i-1 层和第 i-2 层的方法数之和,所以可得递推公式 dp(i) = dp(i-2) + dp(i-1)

动规五部曲:

  1. 确定 dp 数组以及下标的含义

    dp[i] 表示爬到第 i 层楼梯的方法数 (注意:是方法数,不是步数或体力消耗)

  2. 确定递推公式

    dp[i] = dp[i-2] + dp[i-1]

  3. dp 数组初始化

    dp[1] = 1, dp[2] = 2

  4. 确定遍历顺序 从前往后

  5. 举例推导 dp 数组

    当 n=5 时,dp 数组为:[1,2,3,5,8],代码中打印一下

var climbStairs = function (n) {
const dp = [1, 2]
for (let i = 2; i < n; i++) {
dp[i] = dp[i - 2] + dp[i - 1]
}
console.log(dp)
return dp[n - 1]
}

746. 使用最小花费爬楼梯 🌟#

力扣链接 🌟

题目描述#

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 1:

**输入:**cost = [10,15,20] **输出:**15 **解释:**你将从下标为 1 的台阶开始。

  • 支付 15 ,向上爬两个台阶,到达楼梯顶部。 总花费为 15 。

示例 2:

**输入:**cost = [1,100,1,1,1,100,1,1,100,1] **输出:**6 **解释:**你将从下标为 0 的台阶开始。

  • 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
  • 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
  • 支付 1 ,向上爬一个台阶,到达楼梯顶部。 总花费为 6 。

提示:

  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999

解题思路#

先确定条件,每次只能爬一个或两个台阶,并且需要花费相应的体力,如何到达楼梯顶部。

以[1,100,1,1,1,100,1,1,100,1]为例:

想要到达第三个台阶,可以选择:

  1. 从第一个台阶开始,花费 1,到达第三个台阶
  2. 从第二个台阶开始,花费 100,到达第三个台阶

此时选择花费较小的第一个选择,即 1

想要到达第四个台阶,可以选择:

  1. 从第二个台阶开始,花费 100,到达第四个台阶
  2. 从第三个台阶开始,花费 1,到达第四个台阶

此时选择花费较小的第二个选择,即 1 + 1 = 2

那么,想要到达第 i 个台阶时,可以选择:

  1. 从第 i-2 个台阶开始,花费 cost[i-2],到达第 i 个台阶
  2. 从第 i-1 个台阶开始,花费 cost[i-1],到达第 i 个台阶

选择花费较小的选择,即 dp[i-2] + cost[i-2] 或者 dp[i-1] + cost[i-1]

因此可以得到递推公式:dp(i) = Math.min(dp(i-2) + cost[i-2], dp(i-1) + cost[i-1])

动规五部曲

  1. 确定 dp 数组以及下标的含义

    dp[i] 表示到达第 i 个台阶的最小花费

  2. 确定递推公式

    dp[i] = Math.min(dp[i-2] + cost[i-2], dp[i-1] + cost[i-1])

  3. dp 数组初始化

    可以从下标为 0 或者下标为 1 的台阶开始爬楼梯,即dp[0] = 0, dp[1] = 0

  4. 确定遍历顺序:从前往后

  5. 举例推导 dp 数组

    如 cost = [1,100,1,1,1,100,1,1,100,1],dp 数组为:[0,0,1,2,2,3,3,4,4,5,6]

var minCostClimbingStairs = function (cost) {
const dp = [0, 0]
for (let i = 2; i <= cost.length; i++) {
dp[i] = Math.min(dp[i - 2] + cost[i - 2], dp[i - 1] + cost[i - 1])
}
console.log(dp)
return dp[cost.length]
}
跟着卡哥学算法Day 32:动态规划理论 & part1
https://wangxiang.website/posts/算法/dynamic-programming-code/
作者
翔子
发布于
2025-03-15
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
翔子
前端开发工程师
公告
博客已从 VitePress 迁移到 Astro + Firefly 主题,223 篇文章全部保留。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
221
分类
9
标签
28
总字数
411,914
运行时长
0
最后活动
0 天前

文章目录