动规五部曲:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
基础题目
1、斐波那契数
五部曲:
确定dp数组以及下标的含义:第i个斐波那契数值
确定递推公式:dp[i] = dp[i-1]+dp[i-2]
dp数组如何初始化:dp[0]=0,dp[1]=1,其余未知先置为0
确定遍历顺序:从前往后
举例推导dp数组
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];
}
};
2、 爬楼梯
五部曲:
确定dp数组以及下标的含义:爬到第i层楼梯,有dp[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[i] = dp[i-1]+dp[i-2]
dp数组如何初始化:dp[1]=1,dp[2]=2,没有第0阶,所以不用考虑dp[0]
确定遍历顺序:从前往后
举例推导dp数组
class Solution {
public:
int climbStairs(int n) {
//斐波那契数变体
if(n<=1) return n;
vector<int> dp(n+1);
dp[1] = 1;//爬一层只有一种方法
dp[2] = 2;//爬两层有两种方法
//爬到第n层的方式可以是在第n-1层的基础上加一
//还有在n-2层的基础上加二
for(int i = 3;i<=n;i++)
{
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
};
3、使用最小花费爬楼梯
五部曲:
确定dp数组以及下标的含义:到达第i阶台阶所花费的最少体力为dp[i];
确定递推公式:dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);由题目可知,每次可以爬1~2个台阶,dp[i - 1] 跳到 dp[i] 需要花费 dp[i - 1] + cost[i - 1];dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。因为要求最小花费,所以要使用min函数;
dp数组如何初始化:dp[0]=0,dp[1]=0,题目中说可以从0阶或者1阶开始;
确定遍历顺序:从前往后
举例推导dp数组
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
//定义dp数组
vector<int> dp(n+1);//到达第i阶台阶所花费的最少体力为dp[i]
//初始化dp数组,可以从从下标为 0 或下标为 1 的台阶开始爬楼梯
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];
}
};
4、不同路径
五部曲:
确定dp数组以及下标的含义:dp[i][j] = k 表示走到(i,j)位置有k种方法;
确定递推公式:dp[i][j] = dp[i-1][j] + dp[i][j-1];走到第(i,j)位置的话只有两种方法,从(i-1,j)过去,或者(i,j-1)过去;
dp数组如何初始化:dp[i][0]=1,dp[0][j]=1,因为机器人只能向下或者下右移动;
确定遍历顺序:从左向右,从上向下
举例推导dp数组
class Solution {
public:
int uniquePaths(int m, int n) {
//定义dp数组,dp[i][j] = k 表示走到(i,j)位置有k种方法
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;
//走到第(i,j)位置的话只有两种方法,从(i-1,j)过去,或者(i,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];
}
};
5、不同路径Ⅱ
五部曲:
确定dp数组以及下标的含义:dp[i][j] = k 表示走到(i,j)位置有k种方法;
确定递推公式:dp[i][j] = dp[i-1][j] + dp[i][j-1];走到第(i,j)位置的话只有两种方法,从(i-1,j)过去,或者(i,j-1)过去;
dp数组如何初始化:
for(int i=0;i<m;i++)
{
if(obstacleGrid[i][0]==0) dp[i][0] = 1;
else break;
}
for(int j=0;j<n;j++)
{
if(obstacleGrid[0][j]==0) dp[0][j] = 1;
else break;
}
确定遍历顺序:从左向右,从上向下
举例推导dp数组:
for(int i = 0;i<m;i++)
{
for(int j = 0;j<n;j++)
cout<<dp[i][j];
cout<<endl;
}
输出:
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
//定义dp数组,dp[i][j] = k 表示走到(i,j)位置有k种方法
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
vector<vector<int>> dp(m,vector<int>(n,0));
//因为机器人只能向下或者下右移动,遇见障碍物的话当前位置以及之后位置可达方式均为0
for(int i=0;i<m;i++)
{
if(obstacleGrid[i][0]==0) dp[i][0] = 1;
else break;
}
for(int j=0;j<n;j++)
{
if(obstacleGrid[0][j]==0) dp[0][j] = 1;
else break;
}
//走到第(i,j)位置的话只有两种方法,从(i-1,j)过去,或者(i,j-1)过去
for(int i = 1;i<m;i++)
{
for(int j = 1;j<n;j++)
{
//判断一下当前位置是否有障碍物,有的话,当前位置的dp数组值为0
if(obstacleGrid[i][j] == 1)
dp[i][j] = 0;
else
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
6、整数拆分
五部曲:
确定dp数组以及下标的含义:dp[i]表示拆解数字i( 1 <= i <= n)得到的最大乘积为dp[i];
确定递推公式:dp[i] = max(dp[i],max(j*(i-j),j*dp[i-j]));当i被拆分成两个数时,dp[i] = j*(i-j);当i被拆分成两个以上时,dp[i] = j*dp[i-j](例如10拆分成4+3+3,最大乘积既可以由4*dp[6]得到);
dp数组如何初始化:0和1的拆分没有意义,从2开始拆分,2只能拆分成1+1,所以dp[2]=1;
确定遍历顺序:从左向右
举例推导dp数组
for(int i=2;i<=n;i++)
cout<<dp[i]<<' ';
class Solution {
public:
int integerBreak(int n) {
//定义dp数组:dp[i]表示拆解数字i( 1 <= i <= n)得到的最大乘积为dp[i]
vector<int> dp(n+1,0);
//0和1的拆分没有意义,从2开始拆分,2只能拆分成1+1,所以dp[2]=1
dp[2] = 1;
//i从3开始一直到n
for(int i = 3;i <= n;i++)
{
for(int j = 1;j < i;j++)//j代表从i中拆分出来的数,所以小于i
{
//当i被拆分成两个数时,dp[i] = j*(i-j)
//当i被拆分成两个以上时,dp[i] = j*dp[i-j](例如10拆分成4+3+3,最大乘积既可以由4*dp[6]得到)
dp[i] = max(dp[i],max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}
};
7、 不同的二叉搜索树
五部曲:
class Solution {
public:
int numTrees(int n) {
//定义dp数组:dp[i]表示i个节点时有dp[i]种不同的二叉搜索树
vector<int> dp(n+1,0);
//初始化
dp[0] = 1;
//递推开始
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=i;j++)//j是根节点元素
{
dp[i] += dp[j-1]*dp[i-j];
}
}
return dp[n];
}
};