From 80b2aa5a62d70163d7f30fa76112d7b16c1e61a4 Mon Sep 17 00:00:00 2001 From: Mandy8055 Date: Wed, 11 Aug 2021 20:24:25 +0530 Subject: [PATCH 1/6] Added the recursive-memoized approach for coin change combination problem --- Dynamic-Programming/CoinChange.js | 71 ++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/Dynamic-Programming/CoinChange.js b/Dynamic-Programming/CoinChange.js index c7050177b..996ed8ecc 100644 --- a/Dynamic-Programming/CoinChange.js +++ b/Dynamic-Programming/CoinChange.js @@ -1,4 +1,4 @@ -function change (coins, amount) { +function change(coins, amount) { const combinations = new Array(amount + 1).fill(0) combinations[0] = 1 @@ -12,7 +12,47 @@ function change (coins, amount) { return combinations[amount] } -function minimumCoins (coins, amount) { +/** Coin-change combination using recursive approach along with Memoization + * @param {number} amount + * @param {number[]} coins + */ +const changeRecursive = (amount, coins) => { + const mem = new Map() + return coinChangeComb(amount, coins, 0, mem) +} +/** Coin-change combination using recursive approach along with Memoization + * @param {number} amount + * @param {number[]} coins + * @param {number} idx + * @param {Map} mem + */ + +const coinChangeComb = (amount, coins, idx, mem) => { + // Negative Base Case + if (amount < 0 || idx === coins.length) { + return 0 + } + // Positive Base Case + if (amount === 0) { + return 1 + } + + // Main Case + // Check if the recursive function call results is already memoized + if (mem.has(`${amount} - ${idx}`)) { + return mem.get(`${amount} - ${idx}`) + } + let res = 0 + // Consider the coin at index idx + res += coinChangeComb(amount - coins[idx], coins, idx, mem) + // Leave the coin at index idx + res += coinChangeComb(amount, coins, idx + 1, mem) + // Cache the intermediate result in mem + mem.set(`${amount} - ${idx}`, res) + return res +} + +function minimumCoins(coins, amount) { // minimumCoins[i] will store the minimum coins needed for amount i const minimumCoins = new Array(amount + 1).fill(0) @@ -26,7 +66,10 @@ function minimumCoins (coins, amount) { const coin = coins[j] if (coin <= i) { const subRes = minimumCoins[i - coin] - if (subRes !== Number.MAX_SAFE_INTEGER && subRes + 1 < minimumCoins[i]) { + if ( + subRes !== Number.MAX_SAFE_INTEGER && + subRes + 1 < minimumCoins[i] + ) { minimumCoins[i] = subRes + 1 } } @@ -35,11 +78,27 @@ function minimumCoins (coins, amount) { return minimumCoins[amount] } -function main () { +function main() { const amount = 12 const coins = [2, 4, 5] - console.log('Number of combinations of getting change for ' + amount + ' is: ' + change(coins, amount)) - console.log('Minimum number of coins required for amount :' + amount + ' is: ' + minimumCoins(coins, amount)) + console.log( + 'Number of combinations of getting change for ' + + amount + + ' is: ' + + change(coins, amount) + ) + console.log( + 'Number of combinations of getting change for ' + + amount + + ' is: ' + + changeRecursive(coins, amount) + ) + console.log( + 'Minimum number of coins required for amount :' + + amount + + ' is: ' + + minimumCoins(coins, amount) + ) } main() From 46a2aeb7cfe5d2e6599dae0ebf0e2d3e7c3d8b63 Mon Sep 17 00:00:00 2001 From: Mandy8055 Date: Wed, 11 Aug 2021 20:34:45 +0530 Subject: [PATCH 2/6] Fixed the syntactical bug --- Dynamic-Programming/CoinChange.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dynamic-Programming/CoinChange.js b/Dynamic-Programming/CoinChange.js index 996ed8ecc..7c7e6a8a6 100644 --- a/Dynamic-Programming/CoinChange.js +++ b/Dynamic-Programming/CoinChange.js @@ -1,4 +1,4 @@ -function change(coins, amount) { +function change (coins, amount) { const combinations = new Array(amount + 1).fill(0) combinations[0] = 1 @@ -52,7 +52,7 @@ const coinChangeComb = (amount, coins, idx, mem) => { return res } -function minimumCoins(coins, amount) { +function minimumCoins (coins, amount) { // minimumCoins[i] will store the minimum coins needed for amount i const minimumCoins = new Array(amount + 1).fill(0) @@ -78,7 +78,7 @@ function minimumCoins(coins, amount) { return minimumCoins[amount] } -function main() { +function main () { const amount = 12 const coins = [2, 4, 5] console.log( From fa1524498b7d97e353197edc4cea237dba367a81 Mon Sep 17 00:00:00 2001 From: Mandy8055 Date: Fri, 13 Aug 2021 12:53:34 +0530 Subject: [PATCH 3/6] Added 2 Base tests and 5 main tests for CoinChange Problem. Refactored the code and removed the Memoized approach as it was not necessary --- Dynamic-Programming/CoinChange.js | 84 +++----------------- Dynamic-Programming/tests/CoinChange.test.js | 37 +++++++++ 2 files changed, 48 insertions(+), 73 deletions(-) create mode 100644 Dynamic-Programming/tests/CoinChange.test.js diff --git a/Dynamic-Programming/CoinChange.js b/Dynamic-Programming/CoinChange.js index 7c7e6a8a6..df3183918 100644 --- a/Dynamic-Programming/CoinChange.js +++ b/Dynamic-Programming/CoinChange.js @@ -1,58 +1,21 @@ -function change (coins, amount) { +/** + * @params {Array} coins + * @params {Number} amount + */ +export const change = (coins, amount) => { + // Create and initialize the storage const combinations = new Array(amount + 1).fill(0) combinations[0] = 1 - + // Determine the direction of smallest sub-problem for (let i = 0; i < coins.length; i++) { - const coin = coins[i] - - for (let j = coin; j < amount + 1; j++) { - combinations[j] += combinations[j - coin] + // Travel and fill the combinations array + for (let j = coins[i]; j < combinations.length; j++) { + combinations[j] += combinations[j - coins[i]] } } return combinations[amount] } - -/** Coin-change combination using recursive approach along with Memoization - * @param {number} amount - * @param {number[]} coins - */ -const changeRecursive = (amount, coins) => { - const mem = new Map() - return coinChangeComb(amount, coins, 0, mem) -} -/** Coin-change combination using recursive approach along with Memoization - * @param {number} amount - * @param {number[]} coins - * @param {number} idx - * @param {Map} mem - */ - -const coinChangeComb = (amount, coins, idx, mem) => { - // Negative Base Case - if (amount < 0 || idx === coins.length) { - return 0 - } - // Positive Base Case - if (amount === 0) { - return 1 - } - - // Main Case - // Check if the recursive function call results is already memoized - if (mem.has(`${amount} - ${idx}`)) { - return mem.get(`${amount} - ${idx}`) - } - let res = 0 - // Consider the coin at index idx - res += coinChangeComb(amount - coins[idx], coins, idx, mem) - // Leave the coin at index idx - res += coinChangeComb(amount, coins, idx + 1, mem) - // Cache the intermediate result in mem - mem.set(`${amount} - ${idx}`, res) - return res -} - -function minimumCoins (coins, amount) { +function minimumCoins(coins, amount) { // minimumCoins[i] will store the minimum coins needed for amount i const minimumCoins = new Array(amount + 1).fill(0) @@ -77,28 +40,3 @@ function minimumCoins (coins, amount) { } return minimumCoins[amount] } - -function main () { - const amount = 12 - const coins = [2, 4, 5] - console.log( - 'Number of combinations of getting change for ' + - amount + - ' is: ' + - change(coins, amount) - ) - console.log( - 'Number of combinations of getting change for ' + - amount + - ' is: ' + - changeRecursive(coins, amount) - ) - console.log( - 'Minimum number of coins required for amount :' + - amount + - ' is: ' + - minimumCoins(coins, amount) - ) -} - -main() diff --git a/Dynamic-Programming/tests/CoinChange.test.js b/Dynamic-Programming/tests/CoinChange.test.js new file mode 100644 index 000000000..dc98c68c9 --- /dev/null +++ b/Dynamic-Programming/tests/CoinChange.test.js @@ -0,0 +1,37 @@ +import { change } from '../CoinChange' + +test('Base Case 1', () => { + const coins = [2, 3, 5] + const amount = 0 + expect(change(coins, amount)).toBe(1) +}) +test('Base Case 2', () => { + const coins = [] + const amount = 100 + expect(change(coins, amount)).toBe(0) +}) +test('Test Case 1', () => { + const coins = [2, 4, 5] + const amount = 12 + expect(change(coins, amount)).toBe(5) +}) +test('Test Case 2', () => { + const coins = [5, 2, 3, 7, 6, 1, 12, 11, 9, 15] + const amount = 45 + expect(change(coins, amount)).toBe(12372) +}) +test('Test Case 3', () => { + const coins = [2] + const amount = 3 + expect(change(coins, amount)).toBe(0) +}) +test('Test Case 4', () => { + const coins = [3, 5, 7, 8, 9, 10, 11] + const amount = 500 + expect(change(coins, amount)).toBe(35502874) +}) +test('Test Case 5', () => { + const coins = [10] + const amount = 10 + expect(change(coins, amount)).toBe(1) +}) From 43e9ebc702c021456cb07565187a5868b7d81178 Mon Sep 17 00:00:00 2001 From: Mandy8055 Date: Fri, 13 Aug 2021 12:56:42 +0530 Subject: [PATCH 4/6] Added 2 Base tests and 5 main tests for CoinChange Problem. Refactored the code and removed the Memoized approach as it was not necessary --- Dynamic-Programming/CoinChange.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dynamic-Programming/CoinChange.js b/Dynamic-Programming/CoinChange.js index df3183918..254729e02 100644 --- a/Dynamic-Programming/CoinChange.js +++ b/Dynamic-Programming/CoinChange.js @@ -15,7 +15,7 @@ export const change = (coins, amount) => { } return combinations[amount] } -function minimumCoins(coins, amount) { +function minimumCoins (coins, amount) { // minimumCoins[i] will store the minimum coins needed for amount i const minimumCoins = new Array(amount + 1).fill(0) From 2ee9eb965bfe6e160b610cb638dd4288b50f9bf6 Mon Sep 17 00:00:00 2001 From: Mandy8055 Date: Fri, 13 Aug 2021 12:58:55 +0530 Subject: [PATCH 5/6] Added 2 Base tests and 5 main tests for CoinChange Problem. Refactored the code and removed the Memoized approach as it was not necessary --- Dynamic-Programming/CoinChange.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Dynamic-Programming/CoinChange.js b/Dynamic-Programming/CoinChange.js index 254729e02..d320e20db 100644 --- a/Dynamic-Programming/CoinChange.js +++ b/Dynamic-Programming/CoinChange.js @@ -15,28 +15,3 @@ export const change = (coins, amount) => { } return combinations[amount] } -function minimumCoins (coins, amount) { - // minimumCoins[i] will store the minimum coins needed for amount i - const minimumCoins = new Array(amount + 1).fill(0) - - minimumCoins[0] = 0 - - for (let i = 1; i < amount + 1; i++) { - minimumCoins[i] = Number.MAX_SAFE_INTEGER - } - for (let i = 1; i < amount + 1; i++) { - for (let j = 0; j < coins.length; j++) { - const coin = coins[j] - if (coin <= i) { - const subRes = minimumCoins[i - coin] - if ( - subRes !== Number.MAX_SAFE_INTEGER && - subRes + 1 < minimumCoins[i] - ) { - minimumCoins[i] = subRes + 1 - } - } - } - } - return minimumCoins[amount] -} From 9cf1e5585901172fa72038efafdcc9b095f7359d Mon Sep 17 00:00:00 2001 From: Mandy8055 Date: Sun, 15 Aug 2021 15:35:35 +0530 Subject: [PATCH 6/6] Added the tests for coinChangeMin function --- Dynamic-Programming/CoinChange.js | 16 ++++++++++++++++ Dynamic-Programming/tests/CoinChange.test.js | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Dynamic-Programming/CoinChange.js b/Dynamic-Programming/CoinChange.js index d320e20db..35825d7fb 100644 --- a/Dynamic-Programming/CoinChange.js +++ b/Dynamic-Programming/CoinChange.js @@ -15,3 +15,19 @@ export const change = (coins, amount) => { } return combinations[amount] } +/** + * @params {Array} coins + * @params {Number} amount + */ +export const coinChangeMin = (coins, amount) => { + const map = { 0: 1 } + for (let i = 1; i <= amount; i++) { + let min = Infinity + for (const coin of coins) { + if (i < coin) continue + min = Math.min(min, 1 + map[i - coin]) + } + map[i] = min + } + return map[amount] === Infinity ? -1 : map[amount] - 1 +} diff --git a/Dynamic-Programming/tests/CoinChange.test.js b/Dynamic-Programming/tests/CoinChange.test.js index dc98c68c9..6df3c66a3 100644 --- a/Dynamic-Programming/tests/CoinChange.test.js +++ b/Dynamic-Programming/tests/CoinChange.test.js @@ -1,37 +1,44 @@ -import { change } from '../CoinChange' +import { change, coinChangeMin } from '../CoinChange' test('Base Case 1', () => { const coins = [2, 3, 5] const amount = 0 expect(change(coins, amount)).toBe(1) + expect(coinChangeMin(coins, amount)).toBe(0) }) test('Base Case 2', () => { const coins = [] const amount = 100 expect(change(coins, amount)).toBe(0) + expect(coinChangeMin(coins, amount)).toBe(-1) }) test('Test Case 1', () => { const coins = [2, 4, 5] const amount = 12 expect(change(coins, amount)).toBe(5) + expect(coinChangeMin(coins, amount)).toBe(3) }) test('Test Case 2', () => { const coins = [5, 2, 3, 7, 6, 1, 12, 11, 9, 15] const amount = 45 expect(change(coins, amount)).toBe(12372) + expect(coinChangeMin(coins, amount)).toBe(3) }) test('Test Case 3', () => { const coins = [2] const amount = 3 expect(change(coins, amount)).toBe(0) + expect(coinChangeMin(coins, amount)).toBe(-1) }) test('Test Case 4', () => { const coins = [3, 5, 7, 8, 9, 10, 11] const amount = 500 expect(change(coins, amount)).toBe(35502874) + expect(coinChangeMin(coins, amount)).toBe(46) }) test('Test Case 5', () => { const coins = [10] const amount = 10 expect(change(coins, amount)).toBe(1) + expect(coinChangeMin(coins, amount)).toBe(1) })