diff --git a/exercises/practice/change/.approaches/config.json b/exercises/practice/change/.approaches/config.json new file mode 100644 index 000000000..00716db81 --- /dev/null +++ b/exercises/practice/change/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": [ + "jagdish-15" + ] + }, + "approaches": [ + { + "uuid": "d0b615ca-3a02-4d66-ad10-e0c513062189", + "slug": "dynamic-programming", + "title": "Dynamic Programming Approach", + "blurb": "Use dynamic programming to find the most efficient change combination.", + "authors": [ + "jagdish-15" + ], + "contributors": [ + "kahgoh" + ] + } + ] +} diff --git a/exercises/practice/change/.approaches/dynamic-programming/content.md b/exercises/practice/change/.approaches/dynamic-programming/content.md new file mode 100644 index 000000000..7e264778f --- /dev/null +++ b/exercises/practice/change/.approaches/dynamic-programming/content.md @@ -0,0 +1,68 @@ +# Dynamic Programming Approach + +```java +import java.util.List; +import java.util.ArrayList; + +class ChangeCalculator { + private final List currencyCoins; + + ChangeCalculator(List currencyCoins) { + this.currencyCoins = currencyCoins; + } + + List computeMostEfficientChange(int grandTotal) { + if (grandTotal < 0) + throw new IllegalArgumentException("Negative totals are not allowed."); + + List> coinsUsed = new ArrayList<>(grandTotal + 1); + coinsUsed.add(new ArrayList()); + + for (int i = 1; i <= grandTotal; i++) { + List bestCombination = null; + for (int coin: currencyCoins) { + if (coin <= i && coinsUsed.get(i - coin) != null) { + List currentCombination = new ArrayList<>(coinsUsed.get(i - coin)); + currentCombination.add(0, coin); + if (bestCombination == null || currentCombination.size() < bestCombination.size()) + bestCombination = currentCombination; + } + } + coinsUsed.add(bestCombination); + } + + if (coinsUsed.get(grandTotal) == null) + throw new IllegalArgumentException("The total " + grandTotal + " cannot be represented in the given currency."); + + return coinsUsed.get(grandTotal); + } +} +``` + +The **Dynamic Programming (DP)** approach is an efficient way to solve the problem of making change for a given total using a list of available coin denominations. +It minimizes the number of coins needed by breaking down the problem into smaller subproblems and solving them progressively. + +## Explanation + +1. **Initialize Coins Usage Tracker**: + + - If the `grandTotal` is negative, an exception is thrown immediately. + - We create a list `coinsUsed`, where each index `i` stores the most efficient combination of coins that sum up to the value `i`. + - The list is initialized with an empty list at index `0`, as no coins are needed to achieve a total of zero. + +2. **Iterative Dynamic Programming**: + + - For each value `i` from 1 to `grandTotal`, we explore all available coin denominations to find the best combination that can achieve the total `i`. + - For each coin, we check if it can be part of the solution (i.e., if `coin <= i` and `coinsUsed[i - coin]` is a valid combination). + - If so, we generate a new combination by adding the current coin to the solution for `i - coin`. We then compare the size of this new combination with the existing best combination and keep the one with fewer coins. + +3. **Result**: + + - After processing all values up to `grandTotal`, the combination at `coinsUsed[grandTotal]` will represent the most efficient solution. + - If no valid combination exists for `grandTotal`, an exception is thrown. + +## Time and Space Complexity + +- The time complexity of this approach is **O(n * m)**, where `n` is the `grandTotal` and `m` is the number of available coin denominations. This is because we iterate over all coin denominations for each amount up to `grandTotal`. + +- The space complexity is **O(n)** due to the list `coinsUsed`, which stores the most efficient coin combination for each total up to `grandTotal`. diff --git a/exercises/practice/change/.approaches/dynamic-programming/snippet.txt b/exercises/practice/change/.approaches/dynamic-programming/snippet.txt new file mode 100644 index 000000000..25f90e6f5 --- /dev/null +++ b/exercises/practice/change/.approaches/dynamic-programming/snippet.txt @@ -0,0 +1,8 @@ +class ChangeCalculator { + private final List currencyCoins; + + ChangeCalculator(List currencyCoins) { + this.currencyCoins = currencyCoins; + } + // computeMostEfficientChange method +} diff --git a/exercises/practice/change/.approaches/introduction.md b/exercises/practice/change/.approaches/introduction.md new file mode 100644 index 000000000..672aae038 --- /dev/null +++ b/exercises/practice/change/.approaches/introduction.md @@ -0,0 +1,55 @@ +# Introduction + +There is an idiomatic approach to solving "Change." +You can use [dynamic programming][dynamic-programming] to calculate the minimum number of coins required for a given total. + +## General guidance + +The key to solving "Change" is understanding that not all totals can be reached with the available coin denominations. +The solution needs to figure out which totals can be achieved and how to combine the coins optimally. + +## Approach: Dynamic Programming + +```java +import java.util.List; +import java.util.ArrayList; + +class ChangeCalculator { + private final List currencyCoins; + + ChangeCalculator(List currencyCoins) { + this.currencyCoins = currencyCoins; + } + + List computeMostEfficientChange(int grandTotal) { + if (grandTotal < 0) + throw new IllegalArgumentException("Negative totals are not allowed."); + + List> coinsUsed = new ArrayList<>(grandTotal + 1); + coinsUsed.add(new ArrayList()); + + for (int i = 1; i <= grandTotal; i++) { + List bestCombination = null; + for (int coin: currencyCoins) { + if (coin <= i && coinsUsed.get(i - coin) != null) { + List currentCombination = new ArrayList<>(coinsUsed.get(i - coin)); + currentCombination.add(0, coin); + if (bestCombination == null || currentCombination.size() < bestCombination.size()) + bestCombination = currentCombination; + } + } + coinsUsed.add(bestCombination); + } + + if (coinsUsed.get(grandTotal) == null) + throw new IllegalArgumentException("The total " + grandTotal + " cannot be represented in the given currency."); + + return coinsUsed.get(grandTotal); + } +} +``` + +For a detailed look at the code and logic, see the full explanation in the [Dynamic Programming Approach][approach-dynamic-programming]. + +[approach-dynamic-programming]: https://exercism.org/tracks/java/exercises/change/approaches/dynamic-programming +[dynamic-programming]: https://en.wikipedia.org/wiki/Dynamic_programming