You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.

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.

Input: coins = [1, 2, 5], amount = 11
Output: 3
Explanation: "11" can be made up of using least number of coins 5 + 5 + 1.
Input: coins = [2], amount = 3
Output: -1
Explanation: "3" cannot be made up of using given coins.
Input: coins = [1], amount = 0
Output: 0
Explanation: There are 0 ways to make up amount 0.

Contents

In this approach, we will generate all possible ways to to come up with the amount using the combinations from coins array. For every number at index i from coins array, we compute remainder = amount-coins[i] and for this remainder repeat the same process recursively until the remainder becomes 0.

If the remainder becomes 0 that means we have found a way to come up with the amount using numbers from coins array and if the remainder becomes negative, that means there is no possibility.

For example, consider if the coins = [1, 1, 2], and amount = 3. When we try all different combinations using the numbers from array, the decision tree looks like below diagram. From every node, we are trying with all possibilities using coins array elements.

coins change brute force approach decision tree 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 n is the length of coins array and t is the amount value, this is because we are generating a decision tree with possible sub problems by taking possibilities with each number from coins array and depth can go as big as amount.
Space complexity: O(t + n) since we calling the function recursively, this is the stack memory.

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.

Overlapping sub problems

In this approach, we are going to cache the results in a HashMap with key as amount and value as min, that is minimum number of coins needed to make up that amount.

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 n is the length of coins array and t is the amount value. This is because we are looping for all coins from the array and amount number of times by taking remainder using each coin.
Space complexity: O(t * n) for the stack used in recursive calls and the cache that we are maintaining.

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 amount for every number from 0......amount and finally return last indexed value if there is a possibility to make up the amount.

Consider an example with coins = [1, 1, 2] and amount = 3 DP tabulation example

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 n is the length of coins array and t is the amount value. This is because we are looping for all coins from the array and amount number of times by taking remainder using each coin.
Space complexity: O(t) where t is the input amount since the cache is initialized with amount size.

Above implementations source code can be found at GitHub link for Java code