动态规划介绍
五步曲
-
dp 数组的定义和下标。
-
递推公式。
-
dp 数组如何初始化,初始化也需要注意。
-
遍历顺序,比较考究,01 先遍历背包,后遍历物品。
4.1 排列和组合的遍历顺序是不相同的。
4.1.1 排列:背包在外 物品在内。(322)
4.1.2 组合:物品在外,背包在内。(518)
-
(出现问题)打印dp数组。(打印dp数组,检查是否有问题,检验1 2 3 4 步骤)
509. 斐波那契数
思路
讲述看到这一题的思路
解题方法
动规五部曲:
- 确定dp数组以及下标的含义 --> dp[i]的定义为:第i个数的斐波那契数值是dp[i]
- 确定递推公式 --> 状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];
- dp数组如何初始化 --> dp[0] = 0; dp[1] = 1;
- 确定遍历顺序 --> 从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
- 举例推导dp数组
复杂度
-
时间复杂度: O ( n ) O(n) O(n)
-
空间复杂度: O ( n ) O(n) O(n)
Code
class Solution {
// 动规五部曲:
// 1. 确定dp数组以及下标的含义 --> dp[i]的定义为:第i个数的斐波那契数值是dp[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];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
// 5. 举例推导dp数组
public int fib(int n) {
if (n < 2) return n;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
70. 爬楼梯
思路
在到达第n层的上一步,我们只有两个选择,走一步,或者走两步。
如果是走一步,我们需要先通过 f(n-1)种方式到达 n-1 层
如果是走两步, 我们需要通过 f(n-2)种方式到达第 n - 2 层
所以综上有 f(n) = f(n-2) + f(n-1)
解题方法
动规五部曲:
- 定义一个一维数组来记录不同楼层的状态
确定dp数组以及下标的含义
dp[i]: 爬到第i层楼梯,有dp[i]种方法
- 确定递推公式
dp[i] = dp[i - 1] + dp[i - 2] 。
在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。
- dp数组如何初始化
dp[0] 无意义,为方便解题设置为 1
dp[1] 为 1,表示一个台阶时只有一种上楼方式
- 确定遍历顺序
从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的
- 举例推导dp数组
复杂度
-
时间复杂度: O ( n ) O(n) O(n)
-
空间复杂度: O ( n ) O(n) O(n)
Code
class Solution {
// 递推公式
/**
在到达第n层的上一步,我们只有两个选择,走一步,或者走两步。
如果是走一步,我们需要先通过 f(n-1)种方式到达 n-1 层
如果是走两步, 我们需要通过 f(n-2)种方式到达第 n - 2 层
所以综上有 f(n) = f(n-2) + f(n-1)
**/
// 从递推公式上看,就是斐波那契数列的求法,除了 dp[0]没有意义
public int climbStairs(int n) {
int[] dp = new int[n + 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. 使用最小花费爬楼梯
思路
动态规划五步曲
- 确定dp数组以及下标的含义
到达第i台阶所花费的最少体力为dp[i]。
- 确定递推公式
可以有两个途径得到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]。
一定是选最小的,所以 dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
- dp数组如何初始化
dp[i]由dp[i - 1],dp[i - 2]推出,既然初始化所有的dp[i]是不可能的,那么只初始化dp[0]和dp[1]就够了,其他的最终都是dp[0]dp[1]推出。
题目描述中明确说了 “你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。” 也就是说 到达 第 0 个台阶是不花费的,但从 第0 个台阶 往上跳的话,需要花费 cost[0]。
所以初始化 dp[0] = 0,dp[1] = 0;
- 确定遍历顺序
dp[i]由dp[i-1]dp[i-2]推出,所以是从前到后遍历cost数组就可以了。
- 举例推导dp数组
解题方法
描述你的解题方法
复杂度
-
时间复杂度: O ( n ) O(n) O(n)
-
空间复杂度: O ( n ) O(n) O(n)
Code
class Solution {
// 动态规划五步曲
/**
dp[i] 含义:到达下标为 i 的台阶需要的最小花费
递推公式
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
*/
public int minCostClimbingStairs(int[] cost) {
int[] dp = new int[cost.length + 1];
// 初始化时,因为可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯,因此初始消耗都为0
dp[0] = 0;
dp[1] = 0;
for (int i = 2; i < cost.length + 1; i++) {
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[cost.length];
}
}