0

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 ]
}
Ravi L
  • 1,142
  • 9
  • 17

1 Answers1

0

I believe you can simplify your approach and improve the accuracy of the algorithm at the same time. Creating a getCoins function, we start by sorting the available coins. We then loop, getting the highest coin amount less than or equal to the remaining total, until it is zero:

function getCoins(coins, amount) {
    coins.sort((a,b) => b - a);
    const result = [];
    while (amount > 0) {
        let nextCoin = coins.find(coin => coin <= amount);
        if (nextCoin) {; 
            result.push(nextCoin);
            amount -= nextCoin;
        }
    }
    return result;
}

let length = 10;
const coins = [1,2,5];
for(let amount = 1; amount <= 20; amount++) { 
    console.log(`Amount: ${amount}, Coins:`, JSON.stringify(getCoins(coins, amount)));
}
Terry Lennox
  • 29,471
  • 5
  • 28
  • 40
  • 1
    I’m not exactly clear with what’s happening with Array.From line? Let’s say I want to tweak this algo to get also the exact coins that match, how would that work with this approach? – Ravi L Sep 14 '21 at 08:20
  • The Array.from line is just to demonstrate the results for amounts for 1 to 10. It's not necessary for the function at all. To get the min amount of coins just call getCoins with the array of possible coins and the target amount. – Terry Lennox Sep 14 '21 at 08:40
  • The getCoins() function will return the exact coins that match as an array. – Terry Lennox Sep 14 '21 at 09:19
  • 1
    I think I'm still looking to see if it can work with a hash map, similar to this approach. I want to see if it can work with a backtracking approach like above. :https://leetcode.com/problems/coin-change/discuss/77378/Easy-To-Understand-Recursive-DP-solution-using-Java-(with-explanations)/278092/ – Ravi L Sep 14 '21 at 17:49
  • Ah yes that's an interesting approach. – Terry Lennox Sep 14 '21 at 18:09