特别需要注意:
1.动态规划数组(dp数组)的定义以及下标的含义。例如:在李白打酒中f[i][j][k]表示方案数,i表示经过店的数量,j表示经过花的数量,k表示剩余酒的数量。
2.递推公式或状态转移方程
3.dp数组是如何进行初始化的
4.遍历顺序
5.打印dp数组的值
什么时候会用到动态规划数组?
题型:有多少种方案。
枚举大多数情况下不行,搜索通常也不行,但是可能会成功。
所以我们这个时候考虑动态规划。
解题步骤:
1.定义数组:时刻记住你定义的数组的含义及下标。注意有时题上会帮我们定义,但是有时也会误导我们。
2.写状态转移方程。
有两种写法:f[i]由什么转移过来。f[i]可以发展到f[i+1]的什么情况。特别例如李白打酒,就是逆向思维。
3.初始化。
初始化f[0],初始化的方法有两种:根据定义的函数来写,根据实际意思。
4.枚举遍历所有的情况。用子结构递推到最终的结果。
入门例题:
1.斐波那契数列
1 1 2 3 5 8(可以不用动态规划,但非常适合作为动态规划入门,训练一下动态规划步骤和需要注意的问题)
确定dp[i]的含义,dp[i]表示第i个斐波那契数的dp[i]。
递推公式,dp[i]=dp[i-1]+dp[i-2],通过递推关系可以得到
dp数组如何初始化,dp[0]=1,dp[1]=1
遍历顺序,从前向后遍历,这样才能保证dp[i]是由最新的dp[i-1]和dp[i-2]构成
打印dp数组,目的是为了看看我们写的和应该的斐波那契数列是否一样
cs
#include<stdio.h>
int main()
{
long long dp[n+1];
dp[1]=1,dp[0]=1;
int i;
for(i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
printf("%lld\n",dp[n]);
return 0;
}
为什么这是动态规划:
-
最优子结构 :斐波那契数列具有最优子结构性质,
F(n) = F(n-1) + F(n-2);最优子结构是**大问题的最优解 = 小问题的最优解 + 某种组合方式,**如果一个问题的解可以通过组合其子问题的最优解来获得,那么这个问题就具有最优子结构性质。 -
重叠子问题 :计算
F(n)需要重复计算F(n-1)、F(n-2)等子问题。重复计算,直接存储。 -
状态定义 :
dp[i]表示斐波那契数列的第i项 -
状态转移方程 :
dp[i] = dp[i-1] + dp[i-2] -
自底向上计算 :从基础情况
dp[0]和dp[1]开始,逐步计算到目标值
-
✅ 使用数组
dp存储子问题的解 -
✅ 从最小子问题开始求解(
dp[0]和dp[1]) -
✅ 利用已解决的子问题构建更大的解
-
✅ 避免了重复计算(这是动态规划的关键优势)
怎么避免的重复计算?
存储计算结果:
cs
步骤1: dp[0] = 0
步骤2: dp[1] = 1
步骤3: dp[2] = dp[1] + dp[0] = 1 + 0 = 1 ← 计算一次,存储
步骤4: dp[3] = dp[2] + dp[1] = 1 + 1 = 2 ← 直接使用存储的dp[2]
步骤5: dp[4] = dp[3] + dp[2] = 2 + 1 = 3 ← 直接使用存储的dp[3]和dp[2]
步骤6: dp[5] = dp[4] + dp[3] = 3 + 2 = 5 ← 直接使用存储的dp[4]和dp[3]
总计算次数:5次(一次加法计算一个dp值)
和递归的区别:递归没有存储结果的功能
cs
int fib(int n) {
if(n <= 1) return n;
return fib(n-1) + fib(n-2);
}
cs
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1)
/ \ / \
fib(2) fib(1) fib(1) fib(0)
/ \
fib(1) fib(0)
-
fib(5):计算1次 -
fib(4):计算1次 -
fib(3):计算2次(在左子树和右子树各1次) -
fib(2):计算3次 -
fib(1):计算5次 -
fib(0):计算3次
总计: 1+1+2+3+5+3 = 15次函数调用
2.爬楼梯
和上述斐波那契数列基本一样思路。只是初始化不用考虑dp[0],直接从dp[1]=1,dp[2]=2初始化。这里就不细分析了。
dp[i]表示达到第i阶有dp[i]种方法。
3.使用最小花费爬楼梯
给你一个整数数组 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 <= 10000 <= cost[i] <= 999
首先正确理解题目"你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。"也就是说明跳到下标0或下标1是不花费体力的,从下标0或下标1开始跳就要花费体力了。
1.确定dp数组以及下标的含义:是用动态规划,就要有一个数组来记录状态。更具本题意思,只需要一个一维数组即可记录。
dp[i]表示到达i位置的花费最小体力为dp[i]
2.确定递推公式
发现有两个途径可以得到dp[i],一个是走一个台阶dp[i-1],一个是走两个台阶dp[i-2]。
dp[i - 1] 跳到 dp[i] 需要花费 dp[i - 1] + cost[i - 1]。
dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。
那么要选择哪条路径来到达i台阶呢?
当然是选择最小体力花费的,即dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
3.dp数组初始化
看⼀下递归公式,dp[i]由dp[i - 1],dp[i - 2]推出,所以只需要初始化两个就行,不需要初始化整个数组。根据该题只初始化dp[0]和dp[1]即可,其他的最终都是dp[0]dp[1]推出。
那么 dp[0] 应该是多少呢? 根据dp数组的定义,到达第0台阶所花费的最⼩体⼒为dp[0],dp[0] 应该是 cost[0],例如 cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] 的话,dp[0] 就是 cost[0] 应该是1。实际上,根据题意,当跳的时候才会消耗体力,也就是说 到达 第 0 个台阶是不花 费的,但从 第0 个台阶 往上跳的话,需要花费 cost[0]。所以初始化 dp[0] = 0,dp[1] = 0;
4.确定遍历顺序
从前到后遍历cost数组就可以了。
cs
int minCostClimbingStairs(int* cost, int costSize) {
int dp[costSize + 1];
dp[0] = 0;
dp[1] = 0;
for (int i = 2; i <= costSize; i++) {
int option1 = dp[i - 1] + cost[i - 1];
int option2 = dp[i - 2] + cost[i - 2];
dp[i] = option1 < option2 ? option1 : option2;//取最小的体力花费
}
return dp[costSize];
}
上述代码解析:
cs
例子数据:
cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
楼顶位置:下标 10(数组长度 10,最后一个元素下标 9)
逐步计算:
cs
到达台阶2:i=2
从台阶1来:dp[1] + cost[1] = 0 + 100 = 100
从台阶0来:dp[0] + cost[0] = 0 + 1 = 1
dp[2] = min(100, 1) = 1 (选择从台阶0来)
到达台阶3 (i=3)
从台阶2来:dp[2] + cost[2] = 1 + 1 = 2
从台阶1来:dp[1] + cost[1] = 0 + 100 = 100
dp[3] = min(2, 100) = 2 (选择从台阶2来)
到达台阶4 (i=4)
从台阶3来:dp[3] + cost[3] = 2 + 1 = 3
从台阶2来:dp[2] + cost[2] = 1 + 1 = 2
dp[4] = min(3, 2) = 2 (选择从台阶2来)
到达台阶5 (i=5)
从台阶4来:dp[4] + cost[4] = 2 + 1 = 3
从台阶3来:dp[3] + cost[3] = 2 + 1 = 3
dp[5] = min(3, 3) = 3 (两者相同)
............
到达楼顶(台阶10) (i=10)
从台阶9来:dp[9] + cost[9] = 5 + 1 = 6
从台阶8来:dp[8] + cost[8] = 4 + 100 = 104
dp[10] = min(6, 104) = 6 (选择从台阶9来)
cs
最终结果
dp数组 = [0, 0, 1, 2, 2, 3, 3, 4, 4, 5, 6]
最小花费 = dp[10] = 6