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揹包和完全揹包。