|
| 1 | +# Coin Change |
| 2 | + |
| 3 | +Correctly determine the fewest number of coins to be given to a customer such that the sum of the coins' value would |
| 4 | +equal the correct amount of change. |
| 5 | + |
| 6 | +## For example |
| 7 | + |
| 8 | +- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5) |
| 9 | + and one dime (10) or [0, 1, 1, 0, 0] |
| 10 | +- An input of 40 with [1, 5, 10, 25, 100] should return one nickel (5) |
| 11 | + and one dime (10) and one quarter (25) or [0, 1, 1, 1, 0] |
| 12 | + |
| 13 | +## Edge cases |
| 14 | + |
| 15 | +- Does your algorithm work for any given set of coins? |
| 16 | +- Can you ask for negative change? |
| 17 | +- Can you ask for a change value smaller than the smallest coin value? |
| 18 | + |
| 19 | +## Exception messages |
| 20 | + |
| 21 | +Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to |
| 22 | +indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not |
| 23 | +every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include a |
| 24 | +message. |
| 25 | + |
| 26 | +To raise a message with an exception, just write it as an argument to the exception type. For example, instead of |
| 27 | +`raise Exception`, you should write: |
| 28 | + |
| 29 | +```python |
| 30 | +raise Exception("Meaningful message indicating the source of the error") |
| 31 | +``` |
| 32 | + |
| 33 | +## Source |
| 34 | + |
| 35 | +Software Craftsmanship - Coin Change |
| 36 | +Kata [https://web.archive.org/web/20130115115225/http://craftsmanship.sv.cmu.edu:80/exercises/coin-change-kata](https://web.archive.org/web/20130115115225/http://craftsmanship.sv.cmu.edu:80/exercises/coin-change-kata) |
| 37 | + |
| 38 | +## Solution |
| 39 | + |
| 40 | +If we look at the problem, we might immediately think that it could be solved through a greedy approach. However, if we |
| 41 | +look at it closely, we’ll know that it’s not the correct approach here. Let’s take a look at an example to understand why |
| 42 | +this problem can’t be solved with a greedy approach. |
| 43 | + |
| 44 | +Let's suppose we have coins = [1, 3, 4, 5] and we want to find the total = 7 and we try to solve the problem with a greedy |
| 45 | +approach. In a greedy approach, we always start from the very end of a sorted array and traverse backward to find our |
| 46 | +solution because that allows us to solve the problem without traversing the whole array. However, in this situation, we |
| 47 | +start off with a 5 and add that to our total. We then check if it’s possible to get a 7 with the help of either 4 or 3, |
| 48 | +but as expected, that won't be the case, and we would need to add 1 twice to get our required total. |
| 49 | + |
| 50 | +The problem seems to be solved, and we have concluded that we need maximum 3 coins to get to the total of 7. However, if |
| 51 | +we take a look at our array, that isn’t the case. In fact, we could have reached the total of 7 with just 2 coins: 4 and 3. |
| 52 | +So, the problem needs to be broken down into subproblems, and an optimal solution can be reached from the optimal |
| 53 | +solutions of its subproblems. |
| 54 | + |
| 55 | +To split the problem into subproblems, let's assume we know the number of coins required for some total value and the |
| 56 | +last coin denomination is C. Because of the optimal substructure property, the following equation will be true: |
| 57 | + |
| 58 | +Min(total)=Min(total−C)+1 |
| 59 | + |
| 60 | +But, we don't know what is the value of C yet, so we compute it for each element of the coins array and select the |
| 61 | +minimum from among them. This creates the following recurrence relation: |
| 62 | + |
| 63 | +Min(total)=mini=0.....n-1(Min(total−Ci)+1), such that |
| 64 | +Min(total)=0, for total=0 |
| 65 | +Min(total)= -1, for n=0 |
| 66 | + |
| 67 | +> Note: The problem can also be solved with the help of a simple recursive tree without any backtracking, but that would |
| 68 | +> take extra memory and time complexity, as we can see in the illustration below. |
| 69 | +
|
| 70 | + |
| 71 | + |
| 72 | +> Recursive tree for finding minimum number of coins for the total 5 with the coins [1,2,3] |
| 73 | +
|
| 74 | +### Step-by-step solution construction |
| 75 | + |
| 76 | +The idea is to solve the problem using the top-down technique of dynamic programming. If the required total is less than |
| 77 | +the number that’s being evaluated, the algorithm doesn’t make any more recursive calls. Moreover, the recursive tree |
| 78 | +calculates the results of many subproblems multiple times. Therefore, if we store the result of each subproblem in a |
| 79 | +table, we can drastically improve the algorithm’s efficiency by accessing the required value at a constant time. This |
| 80 | +massively reduces the number of recursive calls we need to make to reach our results. |
| 81 | + |
| 82 | +We start our solution by creating a helper function that assists us in calculating the number of coins we need. It has |
| 83 | +three base cases to cover about what to return if the remaining amount is: |
| 84 | + |
| 85 | +- Less than zero |
| 86 | +- Equal to zero |
| 87 | +- Neither less than zero nor equal to zero |
| 88 | + |
| 89 | +> The top-down solution, commonly known as the memoization technique, is an enhancement of the recursive solution. It |
| 90 | +> solves the problem of calculating redundant solutions over and over by storing them in an array. |
| 91 | +
|
| 92 | +In the last case, when the remaining amount is neither of the base cases, we traverse the coins array, and at each |
| 93 | +element, we recursively call the calculate_minimum_coins() function, passing the updated remaining amount remaining_amount |
| 94 | +minus the value of the current coin. This step effectively evaluates the number of coins needed for each possible |
| 95 | +denomination to make up the remaining amount. We store the return value of the base cases for each subproblem in a |
| 96 | +variable named result. We then add 1 to the result variable indicating that we're using this coin denomination in the |
| 97 | +process of making up the corresponding total. Now, we assign this value to minimum, which is initially set to infinity |
| 98 | +at the start of each path. |
| 99 | + |
| 100 | +To avoid recalculating the minimum values for subproblems, we utilize the counter array, which serves as a memoization |
| 101 | +table. This array stores the minimum number of coins required to make up each specific amount of money up to the given |
| 102 | +total. At the end of each path traversal, we update the corresponding index of the counter array with the calculated |
| 103 | +minimum value. Finally, we return the minimum number of coins needed for the given total amount. |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | + |
| 109 | + |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | + |
| 114 | +### Summary |
| 115 | + |
| 116 | +To recap, the solution to this problem can be divided into the following parts: |
| 117 | + |
| 118 | +1. We first check the base cases, if total is either 0 or less than 0: |
| 119 | + - 0 means no new coins need to be added because we have reached a viable solution. |
| 120 | + - Less than 0 means our path can’t lead to this solution, so we need to backtrack. |
| 121 | +2. After this, we use the top-down approach and traverse the given coin denominations. |
| 122 | +3. At each iteration, we either pick a coin or we don’t. |
| 123 | + - If we pick a coin, we move on to solve a new subproblem based on the reduced total value. |
| 124 | + - If we don’t pick a coin, then we look up the answer from the counter array if it is already computed to avoid |
| 125 | + recalculation. |
| 126 | +4. Finally, we return the minimum number of coins required for the given total. |
| 127 | + |
| 128 | +### Time Complexity |
| 129 | + |
| 130 | +The time complexity for the above algorithm is O(n*m). Here, |
| 131 | +n represents the total and m represents the number of coins we have. In the worst case, the height of the recursive tree |
| 132 | +is n as the subproblems solved by the algorithm will be n because we're storing precalculated solutions in a table. Each |
| 133 | +subproblem takes m iterations, one per coin value. So, the time complexity is O(n*m). |
| 134 | + |
| 135 | +### Space Complexity |
| 136 | + |
| 137 | +The space complexity for this algorithm is O(n) because we’re using the counter array which is the size of total. |
0 commit comments