动态规划理论基础
动态规划实质就是动态数组里面的每一个状态都是根据前一个状态来推导出来的
对于动态规划的问题 有以下5个解题步骤
1.明确dp数组的含义以及其下标的含义
2.确定递推公式
3.dp数组应该如何初始化
4.确定遍历顺序
5.举例推导dp数组
509 斐波那契数列
按照动态规划问题的解题步骤 确定dp数组的含义 dp数组代表的就是每个数字的值 根据上面所给的递推公式 初始化dp0=0和dp1=1 dp2由前面两个值得到
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];
}
};
746 使用最小花费爬楼梯
每次可以爬一个或两个台阶 dp数组表示到达每一个台阶花费最少的体力 我们可以从dp[i-1]和dp[i-2]得到dp[i]那么我们就应该从这两种情况选择花费最小的 我们可以选择从下标为0或者为1的台阶开始出发 所以我们初始化dp0和dp1都为0 因为这个时候我们没有花费体力
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(dp0+cost[i-2],dp1+cost[i-1]);
dp0=dp1;
dp1=dpi;
}
return dp1;
}
};
62 不同路径
分析题目 dp[i][j]可以由dp[i-1][j]和dp[i][j-1] 这两个状态得到 dp数组的含义是到达每个格子由多少不同的路径 所以dp[i][j]所有的不同路径是两种情况的和 初始化数组 因为我们遍历顺序肯定是从上往下 从左往右的 所以最上面和最左边都应该初始化为1 因为只有一条路径
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m,vector<int>(n,0));
for(int i=0;i<m;i++){
dp[i][0]=1;
}
for(int j=0;j<n;j++){
dp[0][j]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
343 整数拆分
题目要求最大乘积 所以我们确定dp数组表示每一个值所拆分出来的最大乘积 我们是如何得到dp[i]的 一个是j * (i - j) 直接相乘(相当于拆分成2个数相乘) 一个是j * dp[i - j],相当于是拆分(i - j) (相当于拆分成3个数相乘)初始化数组时 因为dp0和dp1是没有意义的 所以只需要初始化dp2为1 这是没有异议的 (for循环到i/2是优化 因为乘法交换律 同时为什么取两种情况最大值后还要再取dpi的最大值 因为我们是固定了dpi然后再下面分割j 相当于dpi是不断改变的)
class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n+1);
dp[2]=1;
for(int i=3;i<=n;i++){
for(int j=1;j<= i/2;j++){
dp[i]=max(dp[i],max((i-j)*j,dp[i-j]*j));
}
}
return dp[n];
}
};
96 不同的二叉搜索树
如上图再看题目示例1 我们发现每一种情况是头节点为1-n所有情况加起来 观察示例1 并根据二叉搜索树的特性 当头节点为1 它左子树为0 右子树有两个(n=2)有两种情况 头节点为2 左右子树都为1(n=1)只有一种情况 依此类推 头节点的每一种情况相加 左右子树的情况相乘就是dp数组的含义 每个节点所能组成二叉搜索树的个数
class Solution {
public:
int numTrees(int n) {
vector<int> dp(n+1);
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
dp[i]+=dp[j-1]*dp[i-j];
}
}
return dp[n];
}
};