动态规划基础
动态规划中每一个状态一定是由上一个状态推导出来的
动态规划的解题步骤可以拆解为如下五步:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
leetcode 509. 斐波拉契数列
题目链接:斐波拉契数列
class Solution {
public:
int fib(int N) {
if (N <= 1) return N;
vector<int> dp(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];
}
};
时间复杂度:O(n)
空间复杂度:O(n)
为了减小空间复杂度,可以只维护两个数值就可以了,不需要记录整个序列:
class Solution {
public:
int fib(int N) {
if (N <= 1) return N;
int dp[2];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= N; i++) {
int sum = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = sum;
}
return dp[1];
}
};
时间复杂度:O(n)
空间复杂度:O(1)
leetcode 70. 爬楼梯
题目链接:爬楼梯
爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。
那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层。所以到第三层楼梯的状态可以由第二层楼梯和到第一层楼梯状态推导出来,那么就可以使用动态规划了。
- 确定dp数组以及下标的含义
dp[i]: 爬到第i
层楼梯,有dp[i]
种方法 - 确定递推公式
dp[i]
可以有两个方向推出来:
一是dp[i - 1]
,上i-1
层楼梯,有dp[i - 1]
种方法,那么再一步跳一个台阶就是dp[i]
了。
二是dp[i - 2]
,上i-2
层楼梯,有dp[i - 2]
种方法,那么再一步跳两个台阶就是dp[i]
了。
所以dp[i] = dp[i - 1] + dp[i - 2]
- dp数组初始化
dp[1] = 1
,dp[2] = 2
,dp[0]
在本题没有意义。 - 确定遍历顺序
从前向后遍历 - 举例推导
当n = 5
时,dp数组为1,2,3,5,8
,和斐波拉契数列是一致的
版本一:
class Solution {
public:
int climbStairs(int n) {
if (n <= 1) return n; // 因为下面直接对dp[2]操作了,防止空指针
vector<int> dp(n + 1);
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) { // 注意i是从3开始的
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
时间复杂度:O(n)
空间复杂度:O(n)
版本二:
class Solution {
public:
int climbStairs(int n) {
if (n <= 1) return n;
int dp[3];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
int sum = dp[1] + dp[2];
dp[1] = dp[2];
dp[2] = sum;
}
return dp[2];
}
};
时间复杂度:O(n)
空间复杂度:O(1)
leetcode 746. 使用最小花费爬楼梯
题目链接:使用最小花费爬楼梯
- 确定dp数组以及下标的含义
dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]
- 确定递推公式
dp[i - 1]
跳到dp[i]
需要花费dp[i - 1] + cost[i - 1]
dp[i - 2]
跳到dp[i]
需要花费dp[i - 2] + cost[i - 2]
dp[i]
选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
- dp数组初始化
题目中 “你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。” 说明到达第0个台阶是不花费的,从第0个台阶往上跳需要花费 cost[0]。
所以dp[0] = 0
,dp[1] = 0
。 - 遍历顺序
从前到后遍历cost数组 - 举例推导dp数组
版本一:
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size() + 1);
dp[0] = 0; // 默认第一步都是不花费体力的
dp[1] = 0;
for (int i = 2; i <= cost.size(); i++) {
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[cost.size()];
}
};
时间复杂度:O(n)
空间复杂度:O(n)
版本二:
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int dp0 = 0;
int dp1 = 0;
for (int i = 2; i <= cost.size(); i++) {
int dpi = min(dp1 + cost[i - 1], dp0 + cost[i - 2]);
dp0 = dp1; // 记录一下前两位
dp1 = dpi;
}
return dp1;
}
};
时间复杂度:O(n)
空间复杂度:O(1)