题目1:518 零钱兑换Ⅱ
题目链接:518 零钱兑换Ⅱ
题意
整数数组coins表示不同面额的硬币,每一种面额的硬币有无限个 amount表示总金额
计算凑成总金额的硬币组合有多少个
若没有组合凑出总金额,返回0
数组中的元素有无限多个,可以无限次放入背包容量为amount的背包,所以可以使用完全背包解决,最终求解 dp[amount]
注意是组合,不是排序
动态规划
动规五部曲
1)dp数组及下标j的含义
dp[j] 表示装满总金额为j的背包,硬币组合数有dp[j]种
2)dp数组初始化
dp[1] = 1
dp[j] = 0
3)递推公式
dp[j] += dp[j-coins[i]]
4)遍历顺序
先遍历物品后遍历背包,遍历了{coins[1],coins[2]}得到的是组合
!!!!先遍历背包后遍历物品,既有{coins[1],coins[2]} 又有{coins[2],coins[1]}得到的是排列
5)打印dp数组
代码
class Solution {
public:
int change(int amount, vector<int>& coins) {
//定义dp数组,初始化dp数组
vector<int> dp(amount+1, 0);
dp[0] = 1;
//遍历顺序
//先遍历物品后遍历背包
for(int i=0;i<coins.size();i++){
for(int j=coins[i];j<=amount;j++){
dp[j] += dp[j-coins[i]];
}
}
for(int i=0;i<=amount;i++) cout<<dp[i]<<endl;
return dp[amount];
}
};
- 时间复杂度: O(mn),其中 m 是amount,n 是 coins 的长度
- 空间复杂度: O(m)
题目2:组合总和Ⅳ
题目链接:377 组合总和Ⅳ
题意
整数数组nums中元素不同,从nums找出总和为target的元素排列数
数组中的每个元素可以重复使用,装满容量为target的背包,所以可以使用完全背包的思想,最后求解dp[target]
动态规划
动规五部曲
1)确定dp数组及下标j的含义
dp[j] 装满总和为j的背包,有dp[j]种排列方法
2)dp数组初始化
dp[0] = 1
dp[j] = 0
3)递推公式
dp[j] += dp[j-nums[i]]
4)遍历顺序
因为题目要求的是排列数,所以先遍历背包后遍历物品
5)打印dp数组
代码
测试用例有两个数相加超过int的数据,所以需要在if里加上dp[j] < INT_MAX - dp[j - num]
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
//定义dp数组
vector<int> dp(target+1, 0);
//初始化dp数组
dp[0] = 1;
//遍历顺序
//先遍历背包后遍历物品
for(int j=0;j<=target;j++){
for(int i=0;i<nums.size();i++){
if(j>=nums[i] && dp[j]<INT_MAX-dp[j-nums[i]]) dp[j] += dp[j-nums[i]];
}
}
return dp[target];
}
};
- 时间复杂度: O(target * n),其中 n 为 nums 的长度
- 空间复杂度: O(target)
题目3:57 爬楼梯(进阶)
题目链接:57 爬楼梯
题意
爬n层才能到达楼顶 每次可以爬至多m个台阶(1<=m<=n) 有多少种方法爬到楼顶
每次都可以使用爬m个阶梯,这相当于物品,可以重复使用;爬到n层才到达楼顶,这是背包容量
完全背包问题,与顺序有关 装满容量为n的背包有几种方法
动态规划
动规五部曲
1)确定dp数组及下标j的含义
dp[j] 爬到j阶楼梯有dp[j]种方法
2)dp数组初始化
dp[0] = 1j
dp[j] = 0
3)递推公式
dp[j] += dp[i-j]
4)遍历顺序
先遍历背包,后遍历物品
5)打印dp数组
代码 (之所以循环不是从j=0,i=0开始,是因为题目中描写的1<=m<=n,每次至少跨一个台阶,所以循环要从1开始遍历)
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
//定义dp数组
vector<int> dp(n+1, 0);
//初始化dp数组
dp[0] = 1;
//遍历顺序
for(int j=1;j<=n;j++){
for(int i=1;i<=m;i++){//m的取值范围是[1,n]
if(j>=i) dp[j] += dp[j-i];
}
}
// for(int i=0;i<=n;i++) cout<<dp[i]<<endl;
cout<<dp[n]<<endl;
}
- 时间复杂度: O(n * m) m表示最多可以爬m个台阶
- 空间复杂度: O(n)
题目4:322 零钱兑换
题目链接:332 零钱兑换
题意
整数数组coins表示不同面额的硬币,计算可以凑成总和为amount的最小硬币数,若不存在这样的的组合,则返回-1 每种硬币的数量无限
因为题目中要求每种硬币的数量无限个,相当于物品有无限个,凑成容量为amount的背包,背包里面物品个数最少是多少个,最终求解dp[amount]
动态规划
动规五部曲
1)明确dp数组及下标j的含义
dp[j] 凑成金额为j的背包,背包里面硬币个数最少有dp[j]个
2)dp数组初始化
dp[0] = 0 凑成金额为0的背包所需钱币个数一定是0个
dp[j] = INT_MAX
3)递推公式
dp[j] = min(dp[j], dp[j-coins[i]]+1)
4)遍历顺序
因为只要查出最小组合硬币数即可,有无顺序均可
先遍历物品后遍历背包
先遍历背包后遍历物品均可
5)打印dp数组
代码
先遍历物品后遍历背包
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
//定义dp数组
vector<int> dp(amount+1, INT_MAX);
//初始化dp数组
dp[0] = 0;
//遍历顺序
//先遍历物品后遍历背包
for(int i=0;i<coins.size();i++){
for(int j=coins[i];j<=amount;j++){
if(dp[j-coins[i]]!=INT_MAX) dp[j] = min(dp[j], dp[j-coins[i]]+1);
// cout<<dp[j]<<endl;
}
}
if(dp[amount]==INT_MAX) return -1;
return dp[amount];
}
};
- 时间复杂度: O(n * amount),其中 n 为 coins 的长度
- 空间复杂度: O(amount)
先遍历背包后遍历物品
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
//定义dp数组
vector<int> dp(amount+1, INT_MAX);
//初始化dp数组
dp[0] = 0;
//遍历顺序
//先遍历物品后遍历背包
// for(int i=0;i<coins.size();i++){
// for(int j=coins[i];j<=amount;j++){
// if(dp[j-coins[i]]!=INT_MAX) dp[j] = min(dp[j], dp[j-coins[i]]+1);
// // cout<<dp[j]<<endl;
// }
// }
//先遍历背包,后遍历物品
for(int j=0;j<=amount;j++){
for(int i=0;i<coins.size();i++){
if(j>=coins[i] && dp[j-coins[i]]!=INT_MAX) dp[j] = min(dp[j], dp[j-coins[i]]+1);
}
}
if(dp[amount]==INT_MAX) return -1;
return dp[amount];
}
};