Leetcode — 322. Coin Change(中文)

Anj
5 min readJan 17, 2021

問題連結: https://leetcode.com/problems/coin-change/

給予一陣列coins包含你擁有的各幣值, 回傳你最少需要多少個硬幣來湊滿要求的金額amount, 若你無法用現有的硬幣來達成, 則回傳-1
可假設同幣值硬幣可以用無限次

限制:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104
範例: 
Input: coins = [1,2,5], amount = 11
Output: 3
解釋: 最少可用兩個$5硬幣搭配一個$1湊到$11

想法:

找到對的方法來解題是很重要的, 我在看完題目的第一想法是想說可以用backtracking來找用各種硬幣的所有組合,再選出所用硬幣最少的那個組合
但其實對於解這題來說, 其實非常浪費時間又沒有必要, 因為我們只需要知道硬幣數量就好了, 不需知道實際是由哪些硬幣組成的 (也因此這樣的方法在Leetcode上只過了一半的test cases)

既然我們只需要知道長度, 而一個金額都是由更小的金額來組成的, 比如只要我們找到如何付10元的方法, 那我們如果需要付20元的時候, 是不是就可以參考我們怎麼付10元的組合

因此我選擇用動態規劃來解這題, 我用一個陣列來記錄各金錢值時所需的最少硬幣數量, 所以代表我們如果要湊到$i, 我們最少需要dp[i]個硬幣

程式邏輯:

所有在dp陣列的初始值會需要設一個不可能到達的數字,以表示我們目前還沒找到可以湊到這個金額的硬幣組合, 像是-1, 或任何負數, 或題目一開始給的變數amount+1 都可以 (因為若題目給要我們找amount的時候, 代表最少幣值是1的話, 總共需要amount個硬幣, 因此我們不可能最少硬幣組合數會需要比amount還多)

但dp陣列有幾個位置可以例外

  1. 我們將會預設dp[0]為0, 代表我們需要0個硬幣來湊$0
  2. 所有在coins陣列裡面所提到的所有幣值 在dp裡面都會是1, 因為我們只需要1個那個幣值的硬幣就可以達到指定金額

在loop中, 我們每輪都會想要找我們在數值是i的時候, 最少可以多少硬幣就能達成
我們每次都只選一個幣值, 在選這個幣值之後, 我們的目標數值也跟著變小, 因此這時候我們將利用動態規劃的好處- 可以利用先前的努力, 來去看我們之前有沒有找到過"目前數值"所需要的最少硬幣數量, 若有, 那代表我們這輪也可以用 我們剛選的那個硬幣 + "剩下數值的最少硬幣組合" 來組成我們現在所需的數值
每個幣值都試過後, 我們就可得用了某個硬幣後, 所需要最少硬幣數量來組成現在數值i了

⚠️在所有邏輯開始前, 我們必須先把一開始拿到的coins陣列 由小到大排列
有兩個好處:
1. 我們若發現我們拿到的最小的幣值 比指定的數值還要大的話, 可直接回傳-1代表我們沒有辦法用現有硬幣湊錢
2. 當我們發現 “現在位置的硬幣值”是大於 “現在我們要找的數值”時, 我們也可以直接放棄再去拿後面其他幣值的組合們了

👷來看例子當我們有這幾個幣值 coins = [2,3,5].

⚠️在真正的程式裡面, 我們需要從數值1一個一個到目標數值, 找各個數值所需要的最少硬幣數, 但因為我只是想給一個小例子, 我將只會介紹我們在某個數值時候找硬幣組合的過程, 並不會介紹一個一個找每個數值

假設我們現在已經找到$6了, 代表我們前面已經找玩$0~$5所需要花的最少硬幣數量

🔧Step 1. 我們先指定拿第一個硬幣$2, 因此目標數值剩下$4, 我們就會找我們之前在找$4的時候需要幾個硬幣, 查完後發現$4的時候需要兩個硬幣, 因此再加上我們這次已拿的第一個硬幣, 我們目前最少需要3個硬幣才能組成$6

🔧Step 2. 我們第一個硬幣換拿$3, 目標數值剩下$3, 因為我們若要存到$3的時候只需要1個硬幣, 因此再加上去後發現我們這樣的組合只要2個硬幣就可以組成$6

🔧Step 3. 第一個硬幣改拿$5, 目標數值剩$1, 但我們紀錄中 找不到湊成$1的硬幣組, 因此放棄先拿$5的這個組合

🚧試完所有硬幣, 這輪也就結束了, 將會繼續下一個數值 直到我們輪完目標amount

以下為11ms 程式碼, 贏過 87% —

public int coinChange(int[] coins, int amount) {
Arrays.sort(coins);
if(amount!=0 && coins[0]>amount)
return -1;
int[] dp = new int[amount+1];
Arrays.fill(dp,-1);
dp[0] = 0;
for(int coin: coins)
{
if(coin>amount)
break;
dp[coin] = 1;
}
for(int i=1;i<=amount;i++)
{
if(dp[i]==1)
continue;
int minCount = Integer.MAX_VALUE;
for(int coin: coins)
{
if(coin>i)
break;
if(dp[i-coin]==-1)
continue;
minCount = Math.min(minCount, 1+dp[i-coin]);
}
if(minCount == Integer.MAX_VALUE)
dp[i] = -1;
else
dp[i] = minCount;
}
return dp[amount];
}

時間複雜度: O(n); 空間複雜度: O(n)

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Anj
Anj

Written by Anj

Click here to view all the stories posted on this blog: https://anj910.medium.com/bookmark-c95e4c52cf44

No responses yet

Write a response