visit
In the previous article, we solved this problem using a . However, this approach does not always guarantee an optimal solution.
Given an ATM containing N
banknotes of different denominations, write a function that will withdraw the specified sum of money using the minimum possible number of banknotes. The function should return an array of numbers
or null
if the sum cannot be withdrawn.
A classic example is the Fibonacci sequence. Before calculating F(N)
, we calculate F(N-1)
, and so on, until we reach the subproblem F(0)
, for which the answer is 1.
Let's say we have the following banknotes in an ATM: [1000, 500, 100]
, and we want to withdraw 1100
. We can create a table to store the values of F(N)
, where N
is the number of banknotes:
F(0)
We can obtain only the sum of 0 using 0 banknotes:
Sum | Banknotes |
---|---|
0 | [] |
F(1)
We add a banknote of 1000
and get 2 possible combinations:
Sum | Banknotes |
---|---|
0 | [] |
1000 | [1000] |
F(2)
We add another banknote of 500
to the previous table and get 2 new combinations:
Sum | Banknotes |
---|---|
0 | [] |
1000 | [1000] |
500 | [500] |
1500 | [1000, 500] |
F(3)
We add another banknote of 100
to the previous table and get 4 new combinations:
Sum | Banknotes |
---|---|
0 | [] |
1000 | [1000] |
500 | [500] |
1500 | [1000, 500] |
100 | [100] |
1100 | [1000, 100] |
600 | [500, 100] |
1600 | [1000, 500, 100] |
Result
We look for the key 1100
in the table and get the answer [1000, 100]
.
Loop from 1 to N
, where N
is the number of banknotes, and for each n
, do the following:
const withdraw = (total, _coins) => {
coins = [..._coins].sort((a, b) => b - a);
// Main table
// key - sum
// value - array of banknotes
let sums = {0: []};
// Iterator from 1 to N banknotes
for (key in coins) {
const val = coins[key]; // Banknote value
const newSums = {}; // Temporary table
// Iterator over elements of the main table
for ([key, values] of Object.entries(sums)) {
const newKey = parseInt(key) + val; // New sum
newSums[newKey] = [...values, val]; // New set of banknotes
}
sums = {
...newSums,
...sums, // priority to the main table
};
}
return sums[total] || null;
};
To solve this problem, we iterate over N
banknotes, and for each banknote, we iterate over S
elements in the main table. The maximum possible number of rows in the table is equal to the withdrawal amount, considering that we don't store values more than the required value to withdraw.
Therefore, the time complexity of the algorithm is O(NS)
, where N
is the number of banknotes, and S
is the withdrawal amount.
We learned how to use dynamic programming to solve this problem. Unlike greedy algorithms, dynamic programming can guarantee an optimal solution to the ATM problem in all cases. The time complexity of dynamic programming is O(NS)
, where N
is the number of banknotes and S
is the withdrawal amount. Although this complexity is relatively high, it is still better than generating all possible combinations.