Welcome everyone with another problem to solve! Today, we are dealing with coins and the minimum number of coins to obtain a certain amount. We are going to use American coins: specifically, 1, 5, 10, and 25 cents coins. This problem will also be the occasion to seeĀ two different algorithms, one better than the other, and aĀ comparison between two languages: Go and Python.
As always, these problems are provided by the wonderful newsletter DailyCodingProblem, which you can subscribe toĀ
Ready then? Letās jump to the problem!
The problem
This problem was asked by Google in an interview, but itās also a classic problem in math and statistics, expressed in many many versions:
Find the minimum number of coins required to makeĀ
n
Ā cents.You can use standard American denominations, that is, 1Ā¢, 5Ā¢, 10Ā¢, and 25Ā¢.
For example, givenĀ
n = 16
, returnĀ3
Ā since we can make it with a 10Ā¢, a 5Ā¢, and a 1Ā¢.
The problem does not need further explanations: using 1Ā¢, 5Ā¢, 10Ā¢ and 25Ā¢ coins, we need to find the minimum number of coins to reach a target valueĀ n.
Letās put down some initial considerations first and then see how to write the code to solve this. This will be a long article, but stick with me: I promise it will be interesting!
The logic behind the algorithm
First off, letās look at the coins:Ā each one of them, except for the 1Ā¢, is a multiple of 5.Ā This means that it is really easy, with this system, to build amounts that are multiples of 5.
Given this, we can then use theĀ 1Ā¢ to āadjustāĀ the value to the amount that we need: in the example above, we can get a value of 16Ā¢ by using one 10Ā¢ coin, one 5Ā¢ coin, and one 1Ā¢ coin. We can add 1Ā¢ coins until we reach another multiple of 5, at which point it is not convenient anymore, and we should use 5Ā¢, 10Ā¢ or 25Ā¢. For example, to build 30Ā¢, we can use one 25Ā¢ and five 1Ā¢, but at that point we should swap for a 5Ā¢ to use less coins.
So,Ā how many 1Ā¢ coins are we forced to use at most?Ā Since each number ending with a 0 or a 5 is a multiple of 5,Ā we are forced to use at most four 1Ā¢Ā coins before swapping all of them with a 5Ā¢. Hereās a graphic explanation:
Besides that,which coins should we use first?Ā Itās pretty obvious, of course, but using a 25Ā¢ coin is much more worthwhile (ā¦literally, in this case) than using a 10Ā¢ or a 5Ā¢ coin. This is because sinceĀ we are reaching our target amount faster, we are also using fewer coins to do so. So, we should always focus on using higher-value coins first, followed by the others in decreasing order.
Letās recap a bit:
-
First off,Ā we need to find the nearest multiple lesser than our target values. At that point, we need to start adding 1Ā¢ coins. To do so, we can reverse the process: finding the nearest multiple of 5 by subtracting 1Ā¢ coins from the target value;
-
After that,Ā we can add coins for each coin value and update our value, starting from the highest 25Ā¢ to the lowest 5Ā¢ coin,Ā until our coin value exceeds the remaining target value.
Letās try to build some code.
The code (version one)
Hereās a first version of the code, written in Go language:
https://gist.github.com/NicolaM94/46f9098540343593a8cb6b5c86778641?embedable=true
Letās break it down: we first init a variableĀ coins
Ā to store the coins we used during the process. After that, we apply the first point of our previous argument: ourĀ targetValue
Ā is not a multiple of 5 (Ā targetValue % 5 != 0
Ā ) weĀ increase the coin counter by one and decreaseĀ targetValue
Ā by one:Ā This way, we add 1Ā¢ coins until we hit a multiple of 5.
After that, we apply the second point:Ā we add the other coins, starting from the 25Ā¢ one, until they canāt fit in theĀ targetValue
Ā anymore. So whileĀ targetValue
Ā is lesser or equal to 25, we can use 25Ā¢s to reach it. Again, we add one coin at each loop and subtract 25 from theĀ targetValue
Ā to update it. OnceĀ targetValue
Ā is lesser than 25, we canāt use a 25Ā¢ coin to get to it, so we stop.
This same process is also run for the 10Ā¢ coins: once theĀ targetValue
Ā is less than 10, we must stop using 10Ā¢ coins.
At this point, ourĀ targetValue
Ā holds a value that is less than 10. Since the first step, though, we are carrying on a value that must be a multiple of 5. And since we only divided by 25 and 10,Ā targetValue
Ā must still be a multiple of 5. Multiples of 5 below 10 are only 0 and 5, meaning that we only need one more coin or we need none. For this reason, we can simply add to our coin counterĀ targetValue/5
Ā , which will be 1 ifĀ targetValue
Ā is 5 and 0 ifĀ targetValue
Ā is 0.
Then, we simply return the coins counter.
The code (version two)
We can do better, though. In the last paragraph, we talked about ādividing by 25 and 10ā, becauseĀ if we look closer at the process, it really is simple division. Suppose we have aĀ targetValue
Ā of 90. Itās already a multiple of 5, so we skip the first point and start adding 25Ā¢ coins. At each step of the loop, we add one coin and subtract 25 fromĀ targetValue
Ā : This is basically dividing!
After the division,Ā we get rid of the remainder and keep the integer part, and thatās it.Ā For this reason, we can substitute the for loops in the algorithm with a simple division! Hereās the new algorithm:
The algorithm is pretty much the same; the only difference resides in the missing for loops. Here, we substitute them with the simple division ofĀ targetValue
Ā by the coin we are considering. We first add as many coins asĀ targetValue/{coinValue}
Ā and then we subtract the coin value fromĀ targetValue
Ā as many times as the coins we just added. This simplifies the process a lot, as weāll see later when talking about time complexity.
A Python version
Maybe you noticed that there is somethingĀ wrongĀ with the division. For example, for aĀ targetValue = 90
Ā ,Ā targetValue/25 = 3.6
Ā : how come we can simply add the result to our coins, obtaining 3 instead?Ā This is because the Go language, as many others, treats the two un-typed numbers as integers and gives back āthe result of the same type.Ā For this reason, a division between two un-typed integers gives back an integer, the integer part of 3,6. Unlike Go,Ā Python has a more practical approach: the result of any division is always a floating point number.Ā For this reason, the same algorithm in Python would need some more type conversions here and there. Hereās the result:
PS: While doing some research about Pythonās division return type, I also discovered you could use the double divide signĀ //
Ā , which I did not know existed, instead of usingĀ int
Ā for conversion. For example then:
print(90/25) #3.6
print(90//25) #3
Time complexity
Letās start withĀ the first algorithm. Itās pretty clear that itĀ runs in linear time, with a time complexity O(n): the number of operations depends entirely on the size of the inputĀ targetValue
Ā .Ā The first for loop only runs from 1 to 4 times, so we can consider it constant time. The other two for loops run in linear time instead, depending on the size ofĀ targetValue
Ā .
The second algorithm, though, and also its Python variant, of course,Ā is way better: since we were able to remove all the for loops, its execution time does not depend on the input size anymore. This means the algorithm nowĀ runs in constant time O(1), requiring at most 10 steps to give out a solution.
Conclusion
Thatās it for todayās problem: I hope you find it interesting and if you read it to the end, well: thank you so much!
If you found the article useful, please leave a clap or two and share it with someone who could find it useful, too: that would really make my day! I also opened aĀ
As always, a big thanks for reading
Nicola
Also published here.