一、如果是0-1背包,先物再包,逆序
Leetcode416. 分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
for(auto x:nums) sum+=x;
if(sum%2!=0) return false;
//dp[i]表示是否能组合成总数为i
vector<bool> dp(sum/2+1,false);
dp[0]=true;
for(auto y:nums)
{
for(int i=dp.size()-1;i>=0;i--)
{
if(i-y>=0)
dp[i]=dp[i] || dp[i-y];
}
}
return dp[sum/2];
}
};
二、如果是完全背包,先物再包,顺序
Leetcode322零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
可以写出状态转移方程:
1、先确定「状态」,也就是原问题和子问题中变化的变量。由于零钱数量无限,所以唯一的状态就是目标金额 amount。
2、然后确定 dp 函数的定义:当前的目标金额是 n,至少需要 dp(n) 个零钱凑出该金额。
3、然后确定「选择」并择优,也就是对于每个状态,可以做出什么选择改变当前状态。具体到这个问题,无论当前的目标金额是多少,选择就是从面额列表 coins 中选择一个零钱,然后目标金额就会减少:
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
//初始化较大的数,以便于后面min函数计算
vector<int> dp(amount+1,amount+1);
dp[0]=0;
for(auto coin:coins)
{
for(int i=1;i<dp.size();i++)
{
//子问题无解,跳过
if(i-coin<0) continue;
dp[i]=min(dp[i],dp[i-coin]+1);
}
}
return (dp[amount]==amount+1) ? -1:dp[amount];
}
};
还有Leetcode518. 零钱兑换 II
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp(amount+1,0);
dp[0]=1;
for(auto coin:coins)
{
for(int i=1;i<dp.size();i++)
{
if(i-coin<0) continue;
dp[i]+=dp[i-coin];
}
}
return dp[amount];
}
};
三、考虑物品顺序时,先包再物
Leetcode377. 组合总和 Ⅳ
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。请注意,顺序不同的序列被视作不同的组合。
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<unsigned long long> dp(target + 1, 0);
dp[0] = 1;
for(int i = 0; i <= target; i ++){
for(auto x:nums){
if(i - x >= 0) dp[i] += dp[i - x];
}
}
return dp[target];
}
};