Coin Change
You are given an integer array coinsamount
Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made
up by any combination of the coins, return -1
You may assume that you have an infinite number of each kind of coin.
Example 1
Output: 3
Explanation: "11" can be made up of using least number of coins 5 + 5 + 1.
Example 2
Output: -1
Explanation: "3" cannot be made up of using given coins.
Example 3
Output: 0
Explanation: There are 0 ways to make up amount 0.
Contents
Solution 1 - using brute force approach
In this approach, we will generate all possible ways to to come up with the amounticoinsremainder = amount-coins[i]
If the remainder becomes 0amountcoins
For example, consider if the coins = [1, 1, 2]amount = 3coins
-
We will create a function
that takes inputsbruteForceApproach array andcoins and returnsamount as answer.min -
Base cases are:
-
If the input
isamount , then we will return0 .0 -
If the input
is less thanamount , then we will return0 as a way to indicate that we cannot make up amount using the coins given.-1
-
If the input
-
Then, for every number in
array compute the remainder usingcoins , and callamount - coins[i] function recursively withbruteForceApproach array andcoins . Repeat this process until one of the one of the base case is hit. i.e. remainder will become 0 if we can make up amount or remainder will become less than 0 if the amount cannot be made using coins.remainder -
While calling the
function recursively, if a path found to make up the sum, that is return value of the function will be greater than or equal to 0, then increment the count of coins and keep track ofbruteForceApproach variable.min
public class CoinChange {
/* Brute force approach */
static int bruteForceApproach(int[] coins, int amount) {
if (amount == 0) {
return 0;
}
if (amount <0){
return -1;
}
int min = -1;
for(int i=0; i<coins.length; i++) {
int remainder = amount-coins[i];
int count = bruteForceApproach(coins, remainder);
if(count >=0) {
count++;
if(min == -1 || count <min){
min = count;
}
}
}
return min;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(2t + n) time where ncoinstamount
Space complexity: O(t + n) since we calling the function recursively, this is the stack memory.
Solution 2 - using DP memoization
From the above brute force approach, we can notice that there are some overlapping problems, such as sub problems highlighted in the below diagram, likewise there are many other overlapping sub problems.
In this approach, we are going to cache the results in a HashMapamountmin
import java.util.HashMap;
public class CoinChange {
/* DP memoization */
static int dpMemoization(int[] coins, int amount) {
HashMap<Integer, Integer> cache = new HashMap<>();
return dpMemoizationRec(coins, amount, cache);
}
static int dpMemoizationRec(int[] coins, int amount, HashMap<Integer, Integer> cache) {
if (amount == 0) {
return 0;
}
if (amount <0){
return -1;
}
if(cache.containsKey(amount)) {
return cache.get(amount);
}
int min = -1;
for(int i=0; i<coins.length; i++) {
int remainder = amount-coins[i];
int count = bruteForceApproach(coins, remainder);
if(count >=0) {
count++;
if(min == -1 || count <min){
min = count;
}
}
}
cache.put(amount, min);
return min;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(t * n) time where ncoinstamount
Space complexity: O(t * n) for the stack used in recursive calls and the cache that we are maintaining.
Solution 3 - using DP Bottom-Up approach using tabulation and memory optimization
In this approach we will look at solving the problem using Dynamic programming using tabulation caching mechanism.
The time and memory complexity will be same as above solution using memoization, but we can optimize memory in this approach.
In this solution, we are going to create a temporary array to cache minimum number of coins needed to make up amount0......amount
-
We are going to initialize
, that is to make up amountcache[0] = 0 we need0 coins and initialize other indexes with0 as a way to indicate whether we can make up that amount or not.amount + 1Javaint[] cache = new int[amount +1]; // +1 to include 0 amount as well. cache[0] = 0; Arrays.fill(cache, 1, amount+1, amount+1); // here, we are passing array to be filled, starting index, // end index (exclusive) and value to be filled -
When we are at index
(here i represents an amount), we check how many coins needed to come up with that amount:i-
For every coin from the
array, check if the remainder amountcoins is greater than equal to 0, meaning that it is a considerable coin to make up the indexi - coin amount.i -
Update the cache with
, here we are checkingMIN (cache[i], 1 + cache[i-coin]) to check if the remainder amount1 + cache[i-coin] already present in cache and if that is minimum, we take that.i-coinJavacache[i] = Math.min(cache[i], 1 + cache[i-coin]);
-
For every coin from the
-
Finally, if the value at
index in cache array is notamount (what it is initialized with), then simply return that, otherwise returnamount +1 to indicate that amount cannot be made up using coins.-1
Consider an example with coins = [1, 1, 2]amount = 3
-
In the first step we will initialize cache array with
.cache = [0, 4, 4, 4] -
When
, that is for amount 1:i == 1-
Check if we can consider 1st coin
, remainder will be 0, so we can consider it. Now update the cache.1Codecache[1] = MIN (4, 1 + cache[remainder]) = MIN (4, 1 + cache[0]) = MIN(4, 1 + 0) = 1; -
Check if we can consider 2nd coin
, remainder will be 0, so we can consider it. Now update the cache.1Codecache[1] = MIN (1, 1 + cache[remainder]) = MIN (1, 1 + cache[0]) = MIN(1, 1 + 0) = 1; -
Check if we can consider 3rd coin
, remainder will be -1, so we cannot consider this coin as this coin is greater than amount 1.2
-
Check if we can consider 1st coin
-
When
, that is for amount 2:i == 2-
Check if we can consider 1st coin
, remainder will be 1, so we can consider it. Now update the cache.1Codecache[2] = MIN (4, 1 + cache[remainder]) = MIN (4, 1 + cache[1]) = MIN(4, 1 + 1) = 2; -
Check if we can consider 2nd coin
, remainder will be 1, so we can consider it. Now update the cache.1Codecache[2] = MIN (2, 1 + cache[remainder]) = MIN (2, 1 + cache[1]) = MIN(2, 1 + 1) = 2; -
Check if we can consider 3rd coin
, remainder will be 0, so we can consider it. Now update the cache.2Codecache[2] = MIN (2, 1 + cache[remainder]) = MIN (2, 1 + cache[0]) = MIN(2, 1 + 0) = 1;
-
Check if we can consider 1st coin
-
When
, that is for amount 3:i == 3-
Check if we can consider 1st coin
, remainder will be 2, so we can consider it. Now update the cache.1Codecache[2] = MIN (4, 1 + cache[remainder]) = MIN (4, 1 + cache[2]) = MIN(4, 1 + 1) = 2; -
Check if we can consider 2nd coin
, remainder will be 1, so we can consider it. Now update the cache.1Codecache[2] = MIN (2, 1 + cache[remainder]) = MIN (2, 1 + cache[1]) = MIN(2, 1 + 1) = 2; -
Check if we can consider 3rd coin
, remainder will be 1, so we can consider it. Now update the cache.2Codecache[2] = MIN (2, 1 + cache[remainder]) = MIN (2, 1 + cache[1]) = MIN(2, 1 + 1) = 2;
-
Check if we can consider 1st coin
-
Finally, check if
, this value is update that what it is initialized with, so simply rturn that. So, our answer is 2.cache[amount]
import java.util.Arrays;
public class CoinChange {
/* DP tabulation bottom-up approach */
static int dpTabulation(int[] coins, int amount) {
int[] cache = new int[amount + 1];
cache[0] = 0;
Arrays.fill(cache, 1, amount+1, amount+1); // fill the cache with amount +1
// reason why we are using amount+1 as a way to indicate
// whether we can make up the amount using coins not
for(int i=1; i<cache.length; i++) {
for(int coin : coins) {
if(i-coin >=0) {
cache[i] = Math.min(cache[i], 1+ cache[i-coin]);
}
}
}
if(cache[amount] != amount+1) { // meaning that amount indexed element in cache
// has different value than amount+1, what it is initialized with
return cache[amount];
}
return -1;
}
}
Complexity Analysis:
Time complexity: Above code runs in O(t * n) time where ncoinstamount
Space complexity: O(t) where t is the input amount
Above implementations source code can be found at