I'm writing a function to find the least number of coins required to make a certain amount of change. See this problem
This is a Dynamic Programming problem, however, instead of using a traditional array I am trying to use an Object to memoize the results. It's called coinAmounts
.
The map holds an array of the of least number of coins to make a particular sum, for example, to make an amount of 6, you need [5,1].
I have a getCoinsToMakeAmount
and setCoinsToMakeAmount
to edit/get values from the map. As we iterate paths, the setCoins
will update our map if the path we passed it was a smaller combo of coins to make a given sum.
The base approach is a backtracking algo, and then I make use of the coinMap
to memoize sums.
Edit
I believe I am missing an optimization, because the algorithm seems to work with smaller cycles, but with a larger input like this, it seems to not be great.
coins = [3,7,405,436]
sum = 8839
let combos = []
let coinAmounts = {}
let cycles = 0;
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
coinAmounts = {}
combos = []
if(amount === 0) return 0;
coinChangeHelper(coins, 0, [], amount,0);
console.log(coinAmounts)
//console.log(combos);
// console.log("CYCLES: " + cycles)
if(!coinAmounts[amount]) return -1;
return coinAmounts[amount].length;
};
var coinChangeHelper = function(coins, amount, _chosen, desiredAmount, minPath){
let chosen = Object.assign([], _chosen);
cycles++;
// save the current iteration if it's better
setLowestCoinsToMakeAmount(amount, chosen);
if(amount > desiredAmount){
return false;
}
if(amount === desiredAmount){
combos.push(chosen);
return true;
}
// see if we already know how to make the amount requested
// need to do something with this chosen coins
const chosenCoins = getCoinsToMakeAmount(amount);
if(chosenCoins && chosenCoins.length < chosen.length){
return true;
}
//debug(coins, amount, chosenCoins, _chosen)
// like a backtracking algo
for(let i = minPath; i<coins.length; i++){
const coinAmount = coins[i];
// choose a coin
chosen.push(coinAmount)
coinChangeHelper(coins,amount+coinAmount, chosen, desiredAmount, i);
// unchoose the coin
chosen.pop();
}
}
// get a coin value
const getCoinsToMakeAmount = (amount, startCoin) => {
const currentCoinCount = coinAmounts[amount];
if(!currentCoinCount){
return undefined;
}
return currentCoinCount;
}
const setLowestCoinsToMakeAmount = (coinsSum, coinsChosen) =>{
if(coinsChosen.length <= 0) return;
const currentCoinCount = coinAmounts[coinsSum];
if(!currentCoinCount){
coinAmounts[coinsSum] = coinsChosen;
return;
}
if(coinsChosen.length < currentCoinCount.length){
coinAmounts[coinsSum] = coinsChosen;
}
}
// nicely print out the output
var debug = (coins, amt, memo, chosen) => {
let tabs = ''
for(let i =0; i<amt; i++){
tabs = tabs+'\t';
}
console.log(`${tabs} change([${amt}], [${memo}],[${chosen}]`)
}
For a case of (coins=[1,2,5], amount=11), the coinAmounts
contain the best coin amounts to make a certain sum.
{
'1': [ 1 ],
'2': [ 2 ],
'3': [ 1, 2 ],
'4': [ 2, 2 ],
'5': [ 5 ],
'6': [ 1, 5 ],
'7': [ 2, 5 ],
'8': [ 1, 2, 5 ],
'9': [ 2, 2, 5 ],
'10': [ 5, 5 ],
'11': [ 1, 5, 5 ],
'12': [ 2, 5, 5 ],
'13': [ 1, 2, 5, 5 ],
'14': [ 2, 2, 5, 5 ],
'15': [ 5, 5, 5 ]
}