0-1背包:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量且价值总和最大。
思路:
每件物品仅一件,可以选择放与不放。用dp[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值,其状态转移方程定义为:
dp[i][v] = max(dp[i-1][v] + dp[i-1][v-c[i]]+w[i])。
- dp[i-1][v]: 如果不放第i件物品,那么就是前i-1物品放入容量为v的背包中的最大价值。
- dp[i-1][v-c[i]]+w[i]: 如果放入第i件物品,那么前i-1件物品放入容量为v-c[i]的背包中,放入第i件物品所获的最大价值为dp[i-1][v-c[i]]+w[i]。
简化空间复杂度,状态转移方程等价于:
dp[v] = max(dp[v] + dp[v-c[i]]+w[i])。
伪代码:
for i = 1 to N:
for v = V to 0:
dp[v] = max(dp[v], dp[v-c[i]]+w[i])。
1049. 最后一块石头的重量 II
题目描述:
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
- 如果 x == y,那么两块石头都会被完全粉碎;
- 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。
示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
提示:
- 1 <= stones.length <=30
- 1 <= stones[i] <= 1000
思路:
将石头分成两堆,此问题可转换成两堆的重量相差最少,设石头总重量为sum,其中一堆石头的总量为N,则另一对的重量为sum-N,那么我们要求的就是min(sum-2*N),则N越大越好。
将此转换为0-1背包,背包的目标值为石头总重量的一半sum/2,我们希望放入sum/2的背包中的重量N越大越好。
代码:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = 0;
int nums = stones.size();
for(int i=0;i<nums;i++)
{
sum += stones[i];
}
int N = sum / 2;
vector<int> dp(N + 1, 0);
for(int i=0;i<nums;i++)
{
for(int j=N;j>=stones[i]; j--){
dp[j] = dp[j] > dp[j - stones[i]] + stones[i] ? dp[j] : dp[j - stones[i]] + stones[i];
}
}
return sum - 2 * dp[N];
}
};
完全背包:
有N件物品和一个容量为V的背包,每件物品都有无限件可用。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量且价值总和最大。
思路:
0-1背包每件物品只有一件,完全背包则每件物品都有无限件,也就说每件物品有取0件,1件,2件…等等选择。按照0-1背包的思路,用dp[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值,其状态转移方程定义为:
dp[i][v] = max{dp[i-1][v- kc[i]] + kw[i] | 0<=k*c[i]<=V}
伪代码:
for i = 1 to N:
for v = 0 to V:
dp[v] = max(dp[v], dp[v-c[i]]+w[i])
完全背包v循环的顺序正好和0-1背包v循环的顺序相反。0-1背包时,v从V到0,是为了保证此物体被选择一次,而完全背包则有无限件可选择,在考虑加选第i件物品时,需要考虑已经选入第i种物品的子结果,所以完全背包0-v顺序。
322. 零钱兑换
题目描述:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
思路:
可看成完全背包问题,硬币面额作为作为c[i],硬币数量作为w[i],把求最大价值转换成求最小价值。
思路:
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int nums = coins.size();
vector<int> dp(amount+1,amount+1);
dp[0] = 0;
for(int i=0;i<nums;i++)
{
for(int v = coins[i];v<=amount;v++)
{
if(dp[v] > dp[v-coins[i]] + 1){
dp[v] = dp[v-coins[i]] + 1;
}
}
}
return dp[amount] == amount+1 ? -1 : dp[amount];
}
};
518. 零钱兑换 II
题目描述:
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:
输入: amount = 10, coins = [10]
输出: 1
说明:
你可以假设:
- 0 <= amount (总金额) <= 5000
- 1 <= coin (硬币面额) <= 5000
- 硬币种类不超过 500 种
- 结果符合 32 位符号整数
思路:
完全背包问题变形。
- dp[i][j]表示前i种硬币组成j的组合数
- 状态转移方程为:dp[i][j] = dp[i-1][j] + dp[i][j - coins[i-1]]
代码:
class Solution {
public:
int change(int amount, vector<int>& coins) {
int num = coins.size();
vector<vector<int>> dp(num+1, vector<int>(amount+1, 0));
dp[0][0] = 1;
for(int i=1;i<=num;i++){
dp[i][0] = 1;
for(int j=1;j<=amount;j++)
{
if(j >= coins[i-1]){
dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]];
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return dp[num][amount];
}
};
多重背包:
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:
多重背包的思路和完全背包的思路很类似,完全背包无限件(其实最多V/c[i]),多重背包有个数限制n[i],因为每件物品的数量是有限制的,状态转移方程为:
dp[i][v] = max{dp[i-1][v- kc[i]] + kw[i] | 0<=k<=n[i]}
伪代码:
procedure MultiplePack(cost,weight,amount)
if cost*amount>=V
CompletePack(cost,weight)
return
integer k=1
while k<amount
ZeroOnePack(k*cost,k*weight)
amount=amount-k
k=k*2
ZeroOnePack(amount*cost,amount*weight)
多重背包融合了0-1背包和完全背包。