1 DP理论基础
1.1 什么是DP
如果某一问题有很多重叠子问题,使用动态规划是最有效的。
动规是由前一个状态推导出来的,而贪心是局部直接选最优的。
1.2 DP解题步骤
- 确定dp数组(dp table)以及下标的含义
- 确定状态转移公式 / 递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
1.3 动态规划应该如何debug
找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!
做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果 。然后再写代码,如果代码没通过就打印dp数组,看看是不是和自己预先推导的哪里不一样。如果打印出来和自己预先模拟推导是一样的,那么就是自己的递归公式、初始化或者遍历顺序有问题了。如果和自己预先模拟推导的不一样,那么就是代码实现细节有问题。这样才是一个完整的思考过程,而不是一旦代码出问题,就毫无头绪的东改改西改改,最后过不了,或者说是稀里糊涂的过了。
2 509. 斐波那契数
通过一个简单题,讲一下dp套路,AC代码:
cpp
class Solution {
public:
int dp[40]; // step1:确定状态数组和含义
int fib(int n)
{
dp[0] = 0; // step3: 初始化
dp[1] = 1;
for(int i = 2; i <= n;i++) // step4:确定遍历顺序
dp[i] = dp[i-1]+dp[i-2]; // step2:确定转移函数
return dp[n];
}
};
3 70. 爬楼梯
一次做,AC代码:
疑问:怎么判断用搜索还是dp?这题,我没有受过dp训练所以第一反应是用dfs搜索,找到所有符合要求的叶子。
cpp
class Solution {
public:
int dp[50]; // step1:含义: 对于下标i 有多少种方案到第i层
/*
step2:状态转移方程 dp[i] = dp[i-2] + dp[i-1]
step3: dp数组初始化 dp[1] = 1 , dp[2] = 2
step4: 遍历顺序 i递增
step5: 模拟 1,2,3(1 1 1 + 2 1 +1 2 ),5
*/
int climbStairs(int n)
{
// 这题我的第一感觉是搜索 什么时候用dp????
dp[1] = 1;
dp[2] = 2;
for(int i = 3; i <= n; i++)
dp[i] = dp[i-1] + dp[i-2];
return dp[n];
}
};
4 746. 使用最小花费爬楼梯
写简单dp题好有意思md可能是太简单,一次ac:
cpp
class Solution {
public:
int dp[1010]; // 第i --- 表示到达i层的最小花费
/*
dp[i] = min(dp[i-2]+cost[i-2],dp[i-1]+cost[i-1])
dp[0] = 0;
dp[1] = 0;
增序
模拟dp 0 0 10 15
*/
int minCostClimbingStairs(vector<int>& cost)
{
dp[0] = 0;
dp[1] = 0;
int i = 2;
for(; i < cost.size();i++)
dp[i] = min(dp[i-2]+cost[i-2],dp[i-1]+cost[i-1]);
return min(dp[i-2]+cost[i-2],dp[i-1]+cost[i-1]);
}
};