iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
自我挑戰組

30天 LeetCode 演算法實戰:Java 與 Python 解法比較系列 第 19

Day 19|Coin Change:Java 與 Python 實作 Dynamic Programming

  • 分享至 

  • xImage
  •  

一、題目介紹
今天要解的題目是LeetCode的Coin Change

題目會給我們一組不同面額的硬幣,以及一個目標金額amount
每種硬幣可以使用任意次數,要求找出組成目標金額所需要的最少硬幣數量

例如
coins = [1,2,5]
amount = 11

可以使用5 + 5 + 1 = 11
總共需要3枚硬幣
因此答案為3

如果
coins = [2]
amount = 3
因為只有面額2的硬幣,無法組成3
因此答案為-1,代表無法組成目標金額

二、解題思路
這題可以使用Dynamic Programming

我們先定義dp[i]
代表:組成金額i所需要的最少硬幣數量

例如:coins = [1,2,5]
我們可以逐步計算
dp[0] = 0
dp[1] = 1
dp[2] = 1
dp[3] = 2
dp[4] = 2
dp[5] = 1
...
其中dp[0] = 0
因為組成金額0不需要任何硬幣

三、狀態轉移公式
假設現在要計算dp[i]

如果最後使用一枚面額為coin的硬幣,那麼在使用這枚硬幣之前,我們其實已經需要先組成i - coin

而組成i - coin所需要的最少硬幣數量就是dp[i - coin]

再加上現在這一枚硬幣dp[i - coin] + 1

因此我們可以對所有硬幣進行比較dp[i] = min(dp[i], dp[i - coin] + 1)

四、實際範例
coins = [1,2,5]
amount = 11

我們可以從 0 開始逐步計算
https://ithelp.ithome.com.tw/upload/images/20260909/201786693FAW0yxzbe.png

例如計算11
可以使用
1 + dp[10]
2 + dp[9]
5 + dp[6]

對應的硬幣數量
1 + 2 = 3
1 + 3 = 4
1 + 2 = 3

因此dp[11] = 3
最後答案就是3

五、為什麼需要設定「無限大」?
一開始我們並不知道每個金額最少需要幾枚硬幣
因此可以先將DP陣列初始化成一個很大的數字,代表目前還不知道這個金額能不能組成

例如:amount + 1
因為最差情況下,如果有1元硬幣,最多也只需要amount枚
所以amount + 1可以作為一個不可能的初始值

例如:dp = [0, amount + 1, amount + 1, ...]
之後再利用每一種硬幣逐步更新最小值

六、Java實作
https://ithelp.ithome.com.tw/upload/images/20260909/20178669jZjaQT7g74.png

https://ithelp.ithome.com.tw/upload/images/20260909/20178669e283vtnw9R.png

七、Python實作
https://ithelp.ithome.com.tw/upload/images/20260909/20178669zGMxX7i2S8.png

https://ithelp.ithome.com.tw/upload/images/20260909/201786696kFUHAlKJj.png

八、DP的計算流程
以以下為例
coins = [1,2,5]
amount = 5

dp[1]
可以使用一枚1元硬幣:dp[1] = 1

dp[2]
可以直接使用一枚2元:dp[2] = 1

dp[3]
可以1 + 2
所以dp[3] = 2

dp[4]
可以2 + 2
所以dp[4] = 2

dp[5]
可以直接使用5
因此dp[5] = 1
最後dp = [0,1,1,2,2,1]

答案為1

九、時間與空間複雜度
假設:amount = A
而硬幣種類數量為:n
我們需要對每個金額檢查每一種硬幣

因此時間複雜度為:O(A × n)
也就是O(amount × coins.length)

DP陣列需要保存amount + 1個狀態
因此空間複雜度為:O(amount)

https://ithelp.ithome.com.tw/upload/images/20260909/20178669Zv8maRT5gb.png

十、Java與Python解法比較
https://ithelp.ithome.com.tw/upload/images/20260909/201786697cUAIsWcZu.png

十一、實作結果
Leetcode測試結果:Accepted

十二、今日學習心得
今天學習 Coin Change,讓我對 Dynamic Programming 有了更進一步的理解。

前兩天的DP題目中,Day 17 的 Climbing Stairs 是利用前兩個狀態計算目前狀態;Day 18 的 House Robber 則是比較「選擇目前項目」與「不選擇目前項目」的結果。

到了今天的 Coin Change,我需要思考的是:如果最後選擇一枚硬幣,那麼在使用這枚硬幣之前,需要先解決哪一個更小的問題?

例如要組成 11 元,如果最後使用 5 元硬幣,那麼前面就必須先組成 6 元。

因此:dp[11] = dp[6] + 1

再比較使用不同面額硬幣所得到的結果,最後取最小值。

這讓我了解到,DP 最重要的不只是建立陣列,而是要先找出狀態定義與狀態轉移關係

另外,這題也讓我學到如何處理「無法達成目標」的情況。可以先將狀態設定成一個不可能的數值,最後再判斷是否仍然維持這個狀態。

今天最大的收穫是:面對 DP 問題時,可以從「最後一步做了什麼」開始反推,找出完成目前問題之前必須先解決的子問題。


上一篇
Day 18|House Robber:Java 與 Python 實作 Dynamic Programming
下一篇
Day 20|Fibonacci Number:Java 與 Python 實作 Dynamic Programming
系列文
30天 LeetCode 演算法實戰:Java 與 Python 解法比較22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言