Given an Amount to be paid and the currencies to pay with. There is infinite
supply of every currency using combination of which, the given amount is to be
paid.
Print the number of ways by which
the amount can be paid.
INPUT: currencies = {2,3,4} amount = 8 OUTPUT: 2, 2, 2, 2, 2, 2, 4, 2, 3, 3, 4, 4, Number of ways we can pay using given currencies are : 4
Since we are talking about combinations, therefore, order of payments does not matter, that is, [2, 3, 3], [3, 2, 3], [3, 3, 2] all the ways involving same frequency of currencies will be considered as one way.
Solution
Recursive Approach:
In this approach, we will make a recursive call subtracting all the currencies one by one in a loop, that is,
-
- we will subtract the currency on the current index from the amount to be paid and make the recursive call for the remaining amount to be paid.
- We keep a string which keeps track of all the payments made so far, and whenever any call is made, we just concatenate the paid currency in the string.
- Once we reach to a point where the amount to be paid is zero, this becomes our basecase and we print the payments made so far string and return one as this is one way we can pay the required amount.
- If the amount left to be paid become negative, this means that we do not have a perfect combination of coins perfectly adding upto the amount to be paid, therefore it is not a way to pay, hence we return zero from this type of basecase.
- While we make call, we need to make sure that we do not make a payment with a currency having index lower than the current index, as this will lead to the duplications in the combination which we do not want. Therefore, whenever we make calls in loop we initialise our loop variable with the current currency index not from 0th index.
- As at every stage of the amount to be paid, we are making {number of currencies – current index} number of calls, Say n.
- And the initial amount to be paid = x,
- then the worst time complexity of this algorithm is x^n.
package DynamicProgramming; import java.util.Scanner; Public class CoinChange { public static void main(String[] args) { Scanner scn = new Scanner(System.in); int[] currencies = new int[scn.nextInt()]; for (int i = 0; i < currencies.length; i++) { currencies[i] = scn.nextInt(); } int amount = scn.nextInt(); System.out.println( "Number of ways we can pay using given currencies are : " + coinchange(0, amount, currencies, "")); } public static int coinchange(int ci, int remaining, int[] currencies, String paid) { if (remaining == 0) { /* * this means the amount to be paid can be paid * using only the given currencies. */ System.out.println(paid); /* * as this is one of the way to pay, hence it * will contribute +1 number of ways for itself */ return 1; } if (remaining < 0) { /* * if the remaining amount to be paid is negative, * this means that the set of coins we've paid does * not add upto the amount to be paid, hence it is * not one of the way to pay. */ return 0; } int res = 0; for (int i = ci; i < currencies.length; i++) { /* * we must start our loop from current index, as * if we loop through all currencies once again * then there will be a repetition in the ways used * to pay the amount. */ res += coinchange(i, remaining - currencies[i], currencies, paid + Integer.toString(currencies[i]) + ", "); } return res; } } -
Consider the following recursion tree for testcase : Amount = 8,
Currencies = [2,4]
EFFICIENT APPROACH:
- We can clearly see duplication of calls, where result of amount = 4 and amount = 2 are being calculated again and again that is, there is an overlap in most of the subproblems which is increasing the complexity of the algorithm.
- Also, we can see the result for bigger subproblem is being calculated in post order, that is, after we get the result for the smaller subproblems, the result for bigger subproblem is addition of all its child that is, smaller subproblems.
- As this problem has both the properties of Dynamic Programming, which are Overlapping subproblems and Optimal Substructure. Therefore, we will adopt a Dynamic Programming approach to reduce the worst time complexity of the solution.
Algorithm:
- We make an Array to store the result of smaller subproblems, say dp, of size amount + 1,
- because at ith index we store the number of ways to pay an amount = i, so for the having an index ‘amount’ we need to make an array of amount + 1 size.
- We basically process this array for every currency, that is, for every currency we run a loop over the dp array to calculate the total number of ways to pay the amount with number of currencies = current currency index + 1.
- The point to understand for understanding this algorithm is that,
- if current amount – current currency can be paid using this currency then, current amount can also be paid, that is, if (dp[amt – currency]) >= 1 then (dp[amt]++)
- So for every currency at every amount we just need to check if the amount-currency is greater than one that is, if it can be paid. If no, then this amount also cant be paid with the current set of currencies.
- Now as for every currency we are processing every amount ranging from 0 to given amount,
- Hence the worst time complexity of this approach would be O(amount * num_currencies)
package DynamicProgramming; import java.util.ArrayList; import java.util.Scanner; public class CoinChange { public static void main(String[] args) { Scanner scn = new Scanner(System.in); int[] currencies = new int[scn.nextInt()]; for (int i = 0; i < currencies.length; i++) { currencies[i] = scn.nextInt(); } int amount = scn.nextInt(); coinchangeDP(amount, currencies); } public static void coinchangeDP(int amount, int[] currencies) { /* * dp array will contain the number of ways 'i' * amount can be paid using the given currencies, * therefore, we made dp of size amount+1 to have * an index = amount. */ int[] dp = new int[amount + 1]; ArrayList<String>[] payments = new ArrayList[amount+1]; for(int i=0;i<payments.length; i++) { payments[i] = new ArrayList<>() } /* * positive basecase, when we have remaining amount = 0, * this means that we have found one way of paying the * initial amount. */ dp[0] = 1; for (int currency : currencies) { for (int amt = 1; amt < dp.length; amt++) { if(amt-currency >=0 && dp[amt - currency] != 0) { dp[amt] +=1; /* we have made an array of arraylist of strings to * store all the ways of paying the current amount, * therefore, the payments of current amount = * payments of (amt - currency) concatenated * with the current currency*/ payments[amt].add(payments[amt-currency].size()>0? (payments[amt-currency].get(payments[amt-currency].size()-1) + currency + " ") : Integer.toString(currency) + " "); } } } /*number of ways of paying given amount = dp[amount]*/ System.out.println(dp[amount] + "\n" + payments[amount]); } }