give me your money!!1
step1:觀察題面,這可以讓我們了解題的型別。
「編寫乙個函式來計算可以湊成總金額」,可以得出這是一道揹包 dp。
「每種硬幣的數量是無限的」,進一步得出這是道完全揹包。(題型:完全揹包)
「最少的硬幣個數」,證明這要在揹包的前提下,求出最小組成數量。
「多組測試資料」,謹記多組輸入 (論wrong answer與沒有多組輸入)。(注意:多組輸入)
step2:思考解法。
第一步,思考 dp 狀態:
\(dp_\):前 \(i\) 種硬幣湊出面值 \(j\) 的最少幣數。對於當前一種硬幣 \(coins_\) 而言,只有取或不取兩種狀態。
若取,取後的幣數為前 \(i - 1\) 種硬幣湊出面值 \(j-w_\times k\) 的總幣數加上當前種類所需幣數 \(k\)。
若不取,則說明前 \(i - 1\) 種硬幣已經能夠湊出面值 \(j\),不需要再取。
第二步,思考狀態轉移方程:
原本完全揹包的狀態轉移方程是:
\[dp_ = \max\, dp_}+a_\}\ (a_\le j\le amount)
\]但這裡我們並不是求總金額以內最大能湊出的面值,而是求湊成總金額的最少幣數,於是就有:
\[dp_=\min\,\,dp_} + 1\}\ (a_\le j\le amount)
\]通過觀察發現,上述方程可以降維。由於對 \(dp_\) 有影響的只有 \(i - 1\),故可以把前一維抹掉,但需要保證 \(dp_\) 可以被 \(dp_}\) 影響(即 \(dp_\) 被計算時 \(dp_}\) 已經被算出),這才相當於物品 \(i\) 多次被放入揹包,所以列舉當前面值 \(j\) 時要正序。
第三步,打出完全揹包的**,把狀態轉移方程換一下,於是本題的演算法部分就完成啦:
for (int i = 1; i <= n; i++)
}
step3:完成**:
通過資料範圍可以發現,一種硬幣的面額是可以比總金額大的,因此可以預處理淺淺優化一下(雖然沒什麼大的效果)。
因為找的是最小幣數,所以 dp 陣列要初始化成極大值,而前 \(0\) 種硬幣湊成 面值 \(0\) 只需要 \(0\) 種硬幣,由此可得 \(dp_ = 0\)。
輸出時值得注意的是,「如果沒有任何一種硬幣組合能組成總金額,輸出 \(-1\)」;在**中,這意味著「如果 \(dp_\) 沒有被更新,則輸出 \(-1\)」,所以只需要輸出時特判一下 \(dp_\) 若仍是初始值就輸出 \(-1\)。
\**(抵制學術不端行為,拒絕 ctrl + c):
#include using namespace std;
const int n = 1e2 + 5, a = 1e4 + 5, inf = 0x3f3f3f3f;
int n, amount, a[n], dp[a];
/* dp(i, j): 前 i 個硬幣湊出 j 的最少硬幣個數
dp(i, j) = min(dp(i - 1, j - a[i]), dp(i - 1, j));
取這個硬幣 or 不取這個硬幣
*/int main()
dp[0] = 0;
for (int i = 1; i <= n; i++)
}printf("%d\n", dp[amount] == inf ? -1 : dp[amount]); // 可以使用三目運算子來特判
}return 0;
}
快去 ac 『零錢兌換』 叭~ ヾ(≧▽≦*)o
bye bye!
Leetcode題解 零錢兌換
注 用揹包問題思想來理解硬幣找零系列問題 for c in coins 列舉硬幣總數 for j in range m,c 1,1 從大到小列舉金額,確保j c 0.f j min f j f j c 1 return f m if f m float inf else 1 如果為inf說明狀態不可...
零錢兌換 leetcode
思路 建乙個動態陣列dp,大小為amount 1,dp裡面的值初始化為amount 1。dp i 表示總金額i最少可以用dp i 的零錢兌換,如果coins j 比i小,那麼總金額i可以由dp i conis j 再加上這枚零錢構成,dp i min dp i dp i coins j 1 如果dp...
leedcode 零錢兌換
給定不同面額的硬幣 coins 和乙個總金額 amount。編寫乙個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 1。示例1輸入 coins 1,2,5 amount 3 輸出 3 解釋 11 5 5 1示例2輸入 coins 2 amount 3 輸出 ...