有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
步骤分为以下五步:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
509. 斐波那契数
斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1 给你n ,请计算 F(n) 。
示例 1:
- 输入:2
- 输出:1
- 解释:F(2) = F(1) + F(0) = 1 + 0 = 1
int fib(int n){
if(n<=1){
return n;
}
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];
}
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
- 输入: 2
- 输出: 2
dp[i]: 爬到第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]
int climbStairs(int n) {
if(n<=1){
return n;
}
int dp[n+1];
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;++i){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
这道题目还可以继续深化,就是一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。
只需要将第二个循环中的2,换成m,就能用于其他题。
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n+1,0);
dp[0]=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=2;++j){
if(i-j>=0) dp[i]+=dp[i-j];
}
}
return dp[n];
}
};
746. 使用最小花费爬楼梯
数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
示例 1:
- 输入:cost = [10, 15, 20]
- 输出:15
- 解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。
int minCostClimbingStairs(int* cost, int costSize) {
int dp[costSize+1];
dp[0]=0;
dp[1]=0;
for(int i=2;i<=costSize;++i){
dp[i]=fmin(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[costSize];
}
62. 不同路径
一个机器人位于一个
m x n
网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
输入:m = 3, n = 7 输出:28
1、dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。
2、dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。
3、首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。
4、递推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。
int uniquePaths(int m, int n) {
int dp[m][n];
dp[0][0]=1;
//初始化
for(int i=0;i<n;++i){
dp[0][i]=1;
}
for(int j=0;j<m;++j){
dp[j][0]=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];
}
63. 不同路径 II
一个机器人位于一个
m x n
网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用
1
和0
来表示。输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] 输出:2 解释:3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径: 1. 向右 -> 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 -> 向右
int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obstacleGridColSize) {
int n=obstacleGridSize,m=*obstacleGridColSize;
int dp[n][m];
memset(dp, 0, sizeof(dp));
//初始化行和列
for (int i=0; i<n; i++){
if(obstacleGrid[i][0]==1) break;//出现障碍物,停止赋值
dp[i][0]=1;
}
for (int j=0; j<m; j++){
if(obstacleGrid[0][j]==1) break;
dp[0][j]=1;
}
for(int i=1;i<n;++i){
for(int j=1;j<m;++j){
if(obstacleGrid[i][j]==0)
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[n-1][m-1];
}
343. 整数拆分
给定一个正整数
n
,将其拆分为k
个 正整数 的和(k >= 2
),并使这些整数的乘积最大化。返回 你可以获得的最大乘积 。
输入: n = 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1。
1、确定dp数组(dp table)以及下标的含义
dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。
dp[i]的定义将贯彻整个解题过程,下面哪一步想不懂了,就想想dp[i]究竟表示的是啥!
2、确定递推公式
可以想 dp[i]最大乘积是怎么得到的呢?其实可以从1遍历j,然后有两种渠道得到dp[i].
一个是j * (i - j) 直接相乘。
一个是j * dp[i - j],相当于是拆分(i - j),对这个拆分不理解的话,可以回想dp数组的定义。
3.初始化数组
初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是1,这个没有任何异议!
4、确定遍历顺序
确定遍历顺序,先来看看递归公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。
int integerBreak(int n) {
int dp[n+1];
memset(dp,0,sizeof(dp));
for(int i=2;i<=n;++i){
for(int j=1;j<i;++j){
dp[i]=fmax(dp[i],fmax((i-j)*j,dp[i-j]*j));
}
}
return dp[n];
}
96. 不同的二叉搜索树
给你一个整数
n
,求恰由n
个节点组成且节点值从1
到n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。输入:n = 3 输出:5
1、dp[i] : 1到i为节点组成的二叉搜索树的个数为dp[i]。
2、递推公式:dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量
3、从递归公式上来讲,dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量] 中以j为头结点左子树节点数量为0,也需要dp[以j为头结点左子树节点数量] = 1, 否则乘法的结果就都变成0了。所以初始化dp[0] = 1
4、递归公式:dp[i] += dp[j - 1] * dp[i - j]可以看出,节点数为i的状态是依靠 i之前节点数的状态。
5、首先一定是遍历节点数,从递归公式:dp[i] += dp[j - 1] * dp[i - j]可以看出,节点数为i的状态是依靠 i之前节点数的状态。那么遍历i里面每一个数作为头结点的状态,用j来遍历。
int numTrees(int n) {
int dp[n+1];
memset(dp,0,sizeof(dp));
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;++i){
for(int j=1;j<=i;++j){
//对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
//一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
dp[i]+=dp[j-1]*dp[i-j];
}
}
return dp[n];
}