动态规划
基础
动态规划,又名状态转移过程的求解,本质上是由一个状态达到另一个状态时,值的变化,最终影响所求解问题。
强化学习最初的算法中,就有动态规划。
随想录中则是提出了动态规划的五个步骤,我认为可以分为上半部分和下半部分:
上半部分——理论
1 - 确定dp数组的含义:即我们求解的问题是什么
2 - 确定递推公式:当前状态如何由之前的状态推导而来
下班部分——实操
3 - dp数组如何初始化:既然其他状态都由状态推导而来,那么必然就会有个初始状态,往往先确定递推公式,反过来确定初始状态会更容易一些
4 - 确定遍历顺序:这一点往往比较难想,涉及到不同的问题
5 - 举例推导dp数组:这一步是为了验证前面四步是否是正确的
509 斐波那契数 easy
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
这道题,没什么可说的,因为递推公式和初始状态都已经给出来了,照着写题就行,因为求的是最终结果,那么直接用O(1)的空间复杂度来解决就可以了,代码如下:
int fib(int n) {
if (n <= 1) return n;
int res;
int dp1 = 0;
int dp2 = 1;
for (int i = 2; i <= n; i++) {
res = dp1 + dp2;
dp1 = dp2;
dp2 = res;
}
return res;
}
70 爬楼梯 easy
假设你正在爬楼梯。需要
n
阶你才能到达楼顶。每次你可以爬
1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
这道题就可以稍微用用上面提到的五部曲了“
1 - dp[i] 表示爬到第i阶时一共可以有的方法
2 - dp[i] = dp[i - 1] + dp[i - 2],即爬到现在这个台阶可以用的方法,是从下一级和下两级方法的总和
3 - 如果台阶只有一阶,那么方法就只有一种
4 - 楼梯要从下往上走,那么遍历顺序从前往后,就是毋庸置疑的
5 - 举例:以台阶有两级为例,
有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
代码如下:
int climbStairs(int n) {
vector<int> dp(n + 1, 0);
if (n <= 1) return 1;
dp[0] = 1, dp[1] = 1;
for (int i = 2; i <= n; ++i) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
如果要优化一下空间复杂度,可以考虑不用数组,而是直接使用三个变量表示状态之间的关系就可以了。此处省略。
746 使用最小花费爬楼梯 easy
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
还是五部曲:
1 - 确定dp数组的含义:毋庸置疑,就是爬到第i个台阶时,花费的总费用
2 - 推导dp公式:爬到当前台阶,有两种前置状态:从上一个台阶走了一步,从上两个台阶走了两步,别无其他。那么dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i],由于之前的状态必然是要往上走的,所以还要加上当前台阶所需的费用。
3 - 确定初始值:台阶0 和 台阶1都是该台阶往上走的花费
4 - 确定遍历顺序:从前往后(毕竟这是简单题的水平
5 - 举例:省略
这道题的代码我们就用省空间的写法来解,代码如下:
int minCostClimbingStairs(vector<int>& cost) {
int dp0 = cost[0];
int dp1 = cost[1];
for (int i = 2; i < cost.size(); i++) {
int dpi = min(dp0, dp1) + cost[i];
dp0 = dp1; // 记录一下前两位
dp1 = dpi;
}
return min(dp0, dp1);
}