W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
在本節(jié)中,我們先求解另一個常見的背包問題:完全背包,再了解它的一種特例:零錢兌換。
Question
給定
圖 14-22 完全背包問題的示例數(shù)據(jù)
完全背包和 0-1 背包問題非常相似,區(qū)別僅在于不限制物品的選擇次數(shù)。
在完全背包的規(guī)定下,狀態(tài)
從而狀態(tài)轉(zhuǎn)移方程變?yōu)椋?/p>
對比兩道題目的代碼,狀態(tài)轉(zhuǎn)移中有一處從
unbounded_knapsack.cpp
/* 完全背包:動態(tài)規(guī)劃 */
int unboundedKnapsackDP(vector<int> &wgt, vector<int> &val, int cap) {
int n = wgt.size();
// 初始化 dp 表
vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));
// 狀態(tài)轉(zhuǎn)移
for (int i = 1; i <= n; i++) {
for (int c = 1; c <= cap; c++) {
if (wgt[i - 1] > c) {
// 若超過背包容量,則不選物品 i
dp[i][c] = dp[i - 1][c];
} else {
// 不選和選物品 i 這兩種方案的較大值
dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);
}
}
}
return dp[n][cap];
}
由于當前狀態(tài)是從左邊和上邊的狀態(tài)轉(zhuǎn)移而來,因此空間優(yōu)化后應(yīng)該對
這個遍歷順序與 0-1 背包正好相反。請借助圖 14-23 來理解兩者的區(qū)別。
圖 14-23 完全背包的空間優(yōu)化后的動態(tài)規(guī)劃過程
代碼實現(xiàn)比較簡單,僅需將數(shù)組 dp
的第一維刪除。
unbounded_knapsack.cpp
/* 完全背包:空間優(yōu)化后的動態(tài)規(guī)劃 */
int unboundedKnapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {
int n = wgt.size();
// 初始化 dp 表
vector<int> dp(cap + 1, 0);
// 狀態(tài)轉(zhuǎn)移
for (int i = 1; i <= n; i++) {
for (int c = 1; c <= cap; c++) {
if (wgt[i - 1] > c) {
// 若超過背包容量,則不選物品 i
dp[c] = dp[c];
} else {
// 不選和選物品 i 這兩種方案的較大值
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
}
}
}
return dp[cap];
}
背包問題是一大類動態(tài)規(guī)劃問題的代表,其擁有很多的變種,例如零錢兌換問題。
Question
給定
圖 14-24 零錢兌換問題的示例數(shù)據(jù)
零錢兌換可以看作是完全背包的一種特殊情況,兩者具有以下聯(lián)系與不同點。
第一步:思考每輪的決策,定義狀態(tài),從而得到
狀態(tài)
二維
第二步:找出最優(yōu)子結(jié)構(gòu),進而推導(dǎo)出狀態(tài)轉(zhuǎn)移方程
本題與完全背包的狀態(tài)轉(zhuǎn)移方程存在以下兩個差異。
第三步:確定邊界條件和狀態(tài)轉(zhuǎn)移順序
當目標金額為
當無硬幣時,無法湊出任意
大多數(shù)編程語言并未提供 int
的最大值來代替。而這又會導(dǎo)致大數(shù)越界:狀態(tài)轉(zhuǎn)移方程中的
為此,我們采用數(shù)字
最后返回前,判斷
coin_change.cpp
/* 零錢兌換:動態(tài)規(guī)劃 */
int coinChangeDP(vector<int> &coins, int amt) {
int n = coins.size();
int MAX = amt + 1;
// 初始化 dp 表
vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));
// 狀態(tài)轉(zhuǎn)移:首行首列
for (int a = 1; a <= amt; a++) {
dp[0][a] = MAX;
}
// 狀態(tài)轉(zhuǎn)移:其余行列
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 若超過背包容量,則不選硬幣 i
dp[i][a] = dp[i - 1][a];
} else {
// 不選和選硬幣 i 這兩種方案的較小值
dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);
}
}
}
return dp[n][amt] != MAX ? dp[n][amt] : -1;
}
圖 14-25 展示了零錢兌換的動態(tài)規(guī)劃過程,和完全背包非常相似。
圖 14-25 零錢兌換問題的動態(tài)規(guī)劃過程
零錢兌換的空間優(yōu)化的處理方式和完全背包一致。
coin_change.cpp
/* 零錢兌換:空間優(yōu)化后的動態(tài)規(guī)劃 */
int coinChangeDPComp(vector<int> &coins, int amt) {
int n = coins.size();
int MAX = amt + 1;
// 初始化 dp 表
vector<int> dp(amt + 1, MAX);
dp[0] = 0;
// 狀態(tài)轉(zhuǎn)移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 若超過背包容量,則不選硬幣 i
dp[a] = dp[a];
} else {
// 不選和選硬幣 i 這兩種方案的較小值
dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);
}
}
}
return dp[amt] != MAX ? dp[amt] : -1;
}
Question
給定
圖 14-26 零錢兌換問題 II 的示例數(shù)據(jù)
相比于上一題,本題目標是組合數(shù)量,因此子問題變?yōu)椋?strong>前
當前狀態(tài)的組合數(shù)量等于不選當前硬幣與選當前硬幣這兩種決策的組合數(shù)量之和。狀態(tài)轉(zhuǎn)移方程為:
當目標金額為
coin_change_ii.cpp
/* 零錢兌換 II:動態(tài)規(guī)劃 */
int coinChangeIIDP(vector<int> &coins, int amt) {
int n = coins.size();
// 初始化 dp 表
vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));
// 初始化首列
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
}
// 狀態(tài)轉(zhuǎn)移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 若超過背包容量,則不選硬幣 i
dp[i][a] = dp[i - 1][a];
} else {
// 不選和選硬幣 i 這兩種方案之和
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];
}
}
}
return dp[n][amt];
}
空間優(yōu)化處理方式相同,刪除硬幣維度即可。
coin_change_ii.cpp
/* 零錢兌換 II:空間優(yōu)化后的動態(tài)規(guī)劃 */
int coinChangeIIDPComp(vector<int> &coins, int amt) {
int n = coins.size();
// 初始化 dp 表
vector<int> dp(amt + 1, 0);
dp[0] = 1;
// 狀態(tài)轉(zhuǎn)移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 若超過背包容量,則不選硬幣 i
dp[a] = dp[a];
} else {
// 不選和選硬幣 i 這兩種方案之和
dp[a] = dp[a] + dp[a - coins[i - 1]];
}
}
}
return dp[amt];
}
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: