The Coin Change Problem is considered by many to be essential to understanding the paradigm of programming known as Dynamic Programming. The two often are always paired together because the coin change problem encompass the concepts of dynamic programming. For those who don't know about dynamic programming it is according to Wikipedia,
"both a mathematical optimization method and a computer programming method ... it refers to simplifying a complicated problem by breaking it down into simpler sub-problems".
In other words, dynamic problem is a method of programming that is used to simplify a problem into smaller pieces. For example if you were asked simply what is 3 * 89? you perhaps would not know the answer off of your head as you probably know what is 2 * 2. However, if you knew what was 3 * 88 (264) then certainly you can deduce 3 * 89. All you would have to do is add 3 to the previous multiple and you would arrive at the answer of 267. Thus, that is a very simple explanation of what is dynamic programming and perhaps you can now see how it can be used to solve large time complexity problems effectively.
By keeping the above definition of dynamic programming in mind, we can now move forward to the Coin Change Problem. The following is an example of one of the many variations of the coin change problem. Given a list of coins i.e 1 cents, 5 cents and 10 cents, can you determine the total number of combinations of the coins in the given list to make up the number N?
Example 1: Suppose you are given the coins 1 cent, 5 cents, and 10 cents with N = 8 cents, what are the total number of combinations of the coins you can arrange to obtain 8 cents.
Input: N=8
Coins : 1, 5, 10
Output: 2
Explanation:
1 way:
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 8 cents.
2 way:
1 + 1 + 1 + 5 = 8 cents.
All you're doing is determining all of the ways you can come up with the denomination of 8 cents. Eight 1 cents added together is equal to 8 cents. Three 1 cent plus One 5 cents added is 8 cents. So there are a total of 2 ways given the list of coins 1, 5 and 10 to obtain 8 cents.
Example 2: Suppose you are given the coins 1 cent, 5 cents, and 10 cents with N = 10 cents, what are the total number of combinations of the coins you can arrange to obtain 10 cents.
Input : N=10
Coins : 1, 5, 10
Output : 4
Explanation:
1 way:
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 10 cents.
2 way:
1 + 1 + 1 + 1 + 1 + 5 = 10 cents.
3 way:
5 + 5 = 10 cents.
4 way:
10 cents = 10 cents.
Now that we know the problem statement and how to find the solution for smaller values, how would we determine the total number of combinations of coins that add to larger values? We write a program. How do we write the program to compute all of the ways to obtain larger values of N? simple we use dynamic programming. Remember the idea behind dynamic programming is to cut each part of the problem into smaller pieces. Similar to the example at the top of the page. If we don't know the value of 4 * 36 but know the value of 4 * 35 (140), we can just add 4 to that value and get our answer for 4 * 36 which by the way is 144.
Okay so we understand what we have to do, but how is a program going to determine how many ways the list of coins can output N? Well lets look that this example.
N = 12
Index of Array: [0, 1, 2]
Array of coins: [1, 5, 10]
This is a array of coins, 1 cent, 5 cents, and 10 cents. The N is 12 cents. So we need to come up with a method that can use those coin values and determine the number of ways we can make 12 cents.
Thinking dynamically, we need to figure out how to add to previous data. So what that means is we have to add to previous solutions instead of recalculating over the same values. Clearly, we have to iterate through the entire array of coins. We also need a way to see if a coin is larger than the N value.
One way to do this is having an array that counts all the way up to the Nth value.
So ...
Array of ways:
[0, 0, 0 ..... Nth value] in our case it would be up to 12.
The reason for having an array up to the Nth value is so we can determine the number of ways the coins make up the values at the index of Array of ways. We do this because if we can determine a coin is larger than that value at the index then clearly we can't use that coin to determine the combinations of the coins because that coin is larger than that value. This can be better understood with an example.
Using the above numbers as example.
N = 12
Index of Array of Coins:
[0, 1, 2]
Array of coins:
[1, 5, 10]
Index of Array of ways:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Array of ways:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Before we start iterating we have to give a predefined value to our ways array. We must set the first element at index 0 of the ways array to 1. This is because there is 1 way to make the number 0, using 0 coins.
So if we started iterating through all the coins array and compare the elements to the Array of ways we will determine how many times a coin can be used to make the values at the index of the ways array.
For example...
First set ways[0] = 1.
Lets compare the first coin, 1 cent.
N = 12
Index of Array of Coins:
[0, 1, 2]
Array of coins:
[1, 5, 10]
Index of Array of ways:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Array of ways:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Then compare coins[0] to all of the index's
of ways array. If the value of the coin is less
than or equal to the ways index, then
ways[j-coins[i]]+ways[j] is the new value of
ways[j]. We do this because we are
trying to break each part down into smaller
pieces. You will see what is happening as
you continue to read. So comparing each value of the
ways index to the first coin, we get the following.
Index of Array of ways:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Array of ways:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Lets now compare the second coin, 5 cents.
N = 12
Index of Array of Coins:
[0, 1, 2]
Array of coins:
[1, 5, 10]
Index of Array of ways:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Array of ways:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Comparing 5 cents to each of the index and
making that same comparison, if the value
of the coin is smaller than the value of
the index at the ways array then ways[j-coins[i]]+ways[j]
is the new value of ways[j]. Thus we
get the following.
Index of Array of ways:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Array of ways:
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3]
We are determining how many times the second
coin goes into all of the values leading up the
Nth coin. Why are we using all
of the coins? It is to check our previous
result dynamically and update our answer
instead of recalculating all over again.
For example take the element at index 10
the answer is 3 so far. But how did we get 3?
We know that the value of 10-5 is 5 so that
is our j-coins[i] value, that is the
difference of what needs to be made up to
make the amount 10. So we look at index 5 of the
ways array and see it has the value 2, for
the same reason as above, there are so far 2
ways to obtain the value 5. So if there are
2 ways to obtain the value 5 then those ways
plus the current number of ways is the new
updated value of the TOTAL
ways to get the value at index 10.
Lets now compare the third coin, 10 cents.
N = 12
Index of Array of Coins:
[0, 1, 2]
Array of coins:
[1, 5, 10]
Comparing 10 cents to each of the index
and making that same comparison, if the
value of the coin is smaller than the value of the
index at the ways array then
ways[j-coins[i]]+ways[j] is the new value of ways[j].
Thus we get the following.
Index of Array of ways:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Array of ways:
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 4, 4, 4]
So the answer to our example is ways[12] which is 4.
With all of the above in mind, lets have a look at the following program below.
C++
#include <bits/stdc++.h>
using namespace std;
/* We have input values of N and an array Coins
that holds all of the coins. We use data type
of long because we want to be able to test
large values
without integer overflow*/
long getNumberOfWays(long N, vector<long> Coins)
{
// Create the ways array to 1 plus the amount
// to stop overflow
vector<long> ways(N + 1);
// Set the first way to 1 because its 0 and
// there is 1 way to make 0 with 0 coins
ways[0] = 1;
// Go through all of the coins
for(int i = 0; i < Coins.size(); i++)
{
// Make a comparison to each index value
// of ways with the coin value.
for(int j = 0; j < ways.size(); j++)
{
if (Coins[i] <= j)
{
// Update the ways array
ways[j] += ways[(j - Coins[i])];
}
}
}
// Return the value at the Nth position
// of the ways array.
return ways[N];
}
void printArray(vector<long> coins)
{
for(long i : coins)
cout << i << "\n";
}
// Driver Code
int main()
{
vector<long> Coins = { 1, 5, 10 };
cout << "The Coins Array:" << endl;
printArray(Coins);
cout << "Solution:" << endl;
cout << getNumberOfWays(12, Coins) << endl;
}
// This code is contributed by mohit kumar 29
Java
/* We have input values of N and an array Coins
that holds all of the coins. We use data type
of long because we want to be able to test
large values
without integer overflow*/
class getWays {
static long getNumberOfWays(long N, long[] Coins)
{
// Create the ways array to 1 plus the amount
// to stop overflow
long[] ways = new long[(int)N + 1];
// Set the first way to 1 because its 0 and
// there is 1 way to make 0 with 0 coins
ways[0] = 1;
// Go through all of the coins
for (int i = 0; i < Coins.length; i++) {
// Make a comparison to each index value
// of ways with the coin value.
for (int j = 0; j < ways.length; j++) {
if (Coins[i] <= j) {
// Update the ways array
ways[j] += ways[(int)(j - Coins[i])];
}
}
}
// return the value at the Nth position
// of the ways array.
return ways[(int)N];
}
static void printArray(long[] coins)
{
for (long i : coins)
System.out.println(i);
}
public static void main(String args[])
{
long Coins[] = { 1, 5, 10 };
System.out.println("The Coins Array:");
printArray(Coins);
System.out.println("Solution:");
System.out.println(getNumberOfWays(12, Coins));
}
}
Python3
''' We have input values of N and an array Coins
that holds all of the coins. We use data type
of because long we want to be able to test
large values
without integer overflow'''
def getNumberOfWays(N, Coins):
# Create the ways array to 1 plus the amount
# to stop overflow
ways = [0] * (N + 1);
# Set the first way to 1 because its 0 and
# there is 1 way to make 0 with 0 coins
ways[0] = 1;
# Go through all of the coins
for i in range(len(Coins)):
# Make a comparison to each index value
# of ways with the coin value.
for j in range(len(ways)):
if (Coins[i] <= j):
# Update the ways array
ways[j] += ways[(int)(j - Coins[i])];
# return the value at the Nth position
# of the ways array.
return ways[N];
def printArray(coins):
for i in coins:
print(i);
if __name__ == '__main__':
Coins = [1, 5, 10];
print("The Coins Array:");
printArray(Coins);
print("Solution:",end="");
print(getNumberOfWays(12, Coins));
# This code is contributed by 29AjayKumar
C#
/* We have input values of N and
an array Coins that holds all of
the coins. We use data type of
long because we want to be able
to test large values without
integer overflow*/
using System;
public class getWays
{
static long getNumberOfWays(long N, long[] Coins)
{
// Create the ways array to 1 plus the amount
// to stop overflow
long[] ways = new long[(int)N + 1];
// Set the first way to 1 because its 0 and
// there is 1 way to make 0 with 0 coins
ways[0] = 1;
// Go through all of the coins
for (int i = 0; i < Coins.Length; i++)
{
// Make a comparison to each index value
// of ways with the coin value.
for (int j = 0; j < ways.Length; j++)
{
if (Coins[i] <= j)
{
// Update the ways array
ways[j] += ways[(int)(j - Coins[i])];
}
}
}
// return the value at the Nth position
// of the ways array.
return ways[(int)N];
}
static void printArray(long[] coins)
{
foreach (long i in coins)
Console.WriteLine(i);
}
// Driver code
public static void Main(String []args)
{
long []Coins = { 1, 5, 10 };
Console.WriteLine("The Coins Array:");
printArray(Coins);
Console.WriteLine("Solution:");
Console.WriteLine(getNumberOfWays(12, Coins));
}
}
// This code has been contributed by 29AjayKumar
JavaScript
<script>
/* We have input values of N and an array Coins
that holds all of the coins. We use data type
of long because we want to be able to test
large values
without integer overflow*/
function getNumberOfWays(N,Coins)
{
// Create the ways array to 1 plus the amount
// to stop overflow
let ways = new Array(N + 1);
for(let i=0;i<N+1;i++)
{
ways[i]=0;
}
// Set the first way to 1 because its 0 and
// there is 1 way to make 0 with 0 coins
ways[0] = 1;
// Go through all of the coins
for (let i = 0; i < Coins.length; i++) {
// Make a comparison to each index value
// of ways with the coin value.
for (let j = 0; j < ways.length; j++) {
if (Coins[i] <= j) {
// Update the ways array
ways[j] += ways[(j - Coins[i])];
}
}
}
// return the value at the Nth position
// of the ways array.
return ways[N];
}
function printArray(coins)
{
for(let i=0;i<coins.length;i++)
{
document.write(coins[i]+"<br>");
}
}
let Coins=[1, 5, 10];
document.write("The Coins Array:<br>");
printArray(Coins);
document.write("Solution:<br>");
document.write(getNumberOfWays(12, Coins)+"<br>");
// This code is contributed by patel2127
</script>
Output: The Coins Array:
1
5
10
Solution:
4
The time complexity of this implementation is O(N * C), where N is the target value and C is the number of coins. This is because we have two nested loops: one that iterates through each coin, and another that iterates through each value up to N.
The space complexity is O(N), as we are creating a vector to hold the number of ways to make each value up to N. The size of the vector is N+1, so the space complexity is O(N).
Similar Reads
DSA Tutorial - Learn Data Structures and Algorithms DSA (Data Structures and Algorithms) is the study of organizing data efficiently using data structures like arrays, stacks, and trees, paired with step-by-step procedures (or algorithms) to solve problems effectively. Data structures manage how data is stored and accessed, while algorithms focus on
7 min read
Quick Sort QuickSort is a sorting algorithm based on the Divide and Conquer that picks an element as a pivot and partitions the given array around the picked pivot by placing the pivot in its correct position in the sorted array. It works on the principle of divide and conquer, breaking down the problem into s
12 min read
Merge Sort - Data Structure and Algorithms Tutorials Merge sort is a popular sorting algorithm known for its efficiency and stability. It follows the divide-and-conquer approach. It works by recursively dividing the input array into two halves, recursively sorting the two halves and finally merging them back together to obtain the sorted array. Merge
14 min read
Data Structures Tutorial Data structures are the fundamental building blocks of computer programming. They define how data is organized, stored, and manipulated within a program. Understanding data structures is very important for developing efficient and effective algorithms. What is Data Structure?A data structure is a st
2 min read
Bubble Sort Algorithm Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements if they are in the wrong order. This algorithm is not suitable for large data sets as its average and worst-case time complexity are quite high.We sort the array using multiple passes. After the fir
8 min read
Breadth First Search or BFS for a Graph Given a undirected graph represented by an adjacency list adj, where each adj[i] represents the list of vertices connected to vertex i. Perform a Breadth First Search (BFS) traversal starting from vertex 0, visiting vertices from left to right according to the adjacency list, and return a list conta
15+ min read
Binary Search Algorithm - Iterative and Recursive Implementation Binary Search Algorithm is a searching algorithm used in a sorted array by repeatedly dividing the search interval in half. The idea of binary search is to use the information that the array is sorted and reduce the time complexity to O(log N). Binary Search AlgorithmConditions to apply Binary Searc
15 min read
Insertion Sort Algorithm Insertion sort is a simple sorting algorithm that works by iteratively inserting each element of an unsorted list into its correct position in a sorted portion of the list. It is like sorting playing cards in your hands. You split the cards into two groups: the sorted cards and the unsorted cards. T
9 min read
Array Data Structure Guide In this article, we introduce array, implementation in different popular languages, its basic operations and commonly seen problems / interview questions. An array stores items (in case of C/C++ and Java Primitive Arrays) or their references (in case of Python, JS, Java Non-Primitive) at contiguous
4 min read
Sorting Algorithms A Sorting Algorithm is used to rearrange a given array or list of elements in an order. For example, a given array [10, 20, 5, 2] becomes [2, 5, 10, 20] after sorting in increasing order and becomes [20, 10, 5, 2] after sorting in decreasing order. There exist different sorting algorithms for differ
3 min read