动态规划理论基础
- 动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
- 动态规划五部曲
- 确定dp数组(dp table)以及下标的含义(我们要用一个一维dp数组来保存“递归”的结果)
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
- 动态规划如何debug
- 找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!
- 做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果。
- 如果确定思路正确,考虑是不是其他方面出了问题(初始化、遍历顺序等)
509. 斐波那契数
一、做题感受&第一想法
1.递归法
class Solution {
public:
int fib(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
return fib(n-1)+fib(n-2);
}
};
二、学习文章后收获
1.递归五部曲的分析
- 确定dp数组和下标i的含义:dp[i]就是fib(i)
- 确定递归公式:dp[i] = dp[i-1]+dp[i-2]
- dp数组的初始化:dp[0] = 0,dp[1] = 1
- 确定遍历顺序:因为dp[i]靠dp[i-1]和dp[i-2]来确定,所以应该从前往后遍历( i 从小到大)。
- 举例推导dp数组:比如如果N = 10:0 1 1 2 3 5 8 13 21 34 55
2.动态规划代码
申请一个长度为(n+1)的vector,用于记录整个dp数组
class Solution {
public:
int fib(int n) {
if(n <= 1) return n;
vector<int> dp(n+1,0); //大小不是n!
dp[1] = 1;
for(int i = 2;i <= n;i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
3.优化后的代码
不需要长度为n+1的vector数组,只需要2个变量即可
class Solution {
public:
int fib(int n) {
if(n <= 1) return n;
int result = 0;
vector<int> dp(2);
dp[0] = 0;
dp[1] = 1;
for(int i = 2;i <= n;i++){
result = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = result;
}
return result;
}
};
70. 爬楼梯
一、做题感受&第一想法
- 动态规划五部曲:
- 确定dp[i]和i含义:dp[i]:爬到第i层台阶时可用的方法总数
- 递推公式:dp[i] = dp[i-1] + dp[i-2]
- dp初始值:dp[1] = 1,dp[2] = 2
- 遍历顺序:从前往后
- 序列:1,2,3,5,8,……(其实就是斐波那契数列!唯一的区别是,没有讨论dp[0]应该是什么,因为dp[0]在本题没有意义!)
class Solution {
public:
int climbStairs(int n) {
//dp[i]:爬到第i层台阶时可用的方法总数
//递推:dp[i] = dp[i-1] + dp[i-2];
if(n <= 2) return n;
int dp1 = 1, dp2 = 2, cur = 0;
for(int i = 3;i <= n;i++){ //要求dp[n],所以i可以取到n
cur = dp1 + dp2;
dp1 = dp2;
dp2 = cur;
}
return cur;
}
};
二、学习文章后收获
1.拓展问题:
- 卡码网:一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。
- 先初始化dp的前m个元素,再按照爬楼梯问题求dp[n]
- 注意点: 初始化dp的前m个元素时,不要忘记加上“从第一层直接跳到该层”的这“1种”方法!
#include<iostream>
#include<vector>
using namespace std;
int main(){
int m = 0,n = 0;
scanf("%d %d",&n,&m);
vector<int> dp(n+1,0);
dp[1] = 1;
//初始化前m个dp元素
for(int i = 2;i <= m;i++){
for(int j = 1;j < i;j++){
dp[i] += dp[j]; //把前面所有元素的方法数都加起来
}
dp[i]++; //注意点:加上从第一层直接跳到第i层的“1”种方法!!!
}
for(int i = m + 1;i <= n;i++){
for(int j = 1;j <= m;j++){
dp[i] += dp[i - j]; //把前面m个元素的方法数都加起来
}
}
cout << dp[n] <<endl;
return 0;
}
三、过程中遇到的问题
1.编译器选错导致的报错
Main.c:1:9: fatal error: iostream: No such file or directory
1 | #include
| ^~~~~~~~~~
compilation terminated.
错误原因:我把编译环境选成C了,然而我写的C++程序。。。
746. 使用最小花费爬楼梯
一、做题感受&第一想法
- 动态规划五要素:
- dp和i的含义:dp[i]表示到第i个台阶时,最小花费
- 递推公式:
dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
- dp初始化:dp[0] = 0, dp[1] = 0;
- 遍历顺序:从前到后
- 手算序列:(略,没法写,不知道cost[]。)
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
vector<int> dp(n + 1,0); //到第i个台阶的最小花费。最后一个元素dp[n],到楼顶
dp[0] = 0;
dp[1] = 0;
for(int i = 2;i <= n;i++){
dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]); //递推公式
}
return dp[n];
}
};