The brute-force solution uses naive recursion, leading to:
coins
array is sorted in ascending order. This allows the solution to break the loop early when a coin is too large for the remaining amount, optimizing the recursive calls.memo
is created to store the minimum number of coins needed for specific amounts. It is initialized with the base case memo[0] = 0
, as zero coins are needed to make an amount of 0.min_coins(amt)
computes the minimum number of coins needed to make the amount amt
:
amt
is already in memo
, return the stored value memo[amt]
, avoiding recomputation.minn
to float('inf')
to track the minimum number of coins. Iterate through each coin in coins
:
diff = amt - coin
.diff < 0
, break the loop, as larger coins (due to sorting) will also be too big.min_coins(diff)
to get the minimum coins needed for diff
, add 1 for the current coin, and update minn
with the minimum of its current value and 1 + min_coins(diff)
.minn
in memo[amt]
and return it as the result for the current amount.min_coins(amount)
to compute the minimum coins needed for the target amount
. If the result is less than float('inf')
, return it as the minimum number of coins. Otherwise, return -1, indicating that the amount cannot be made with the given coins.
dp
of size amount + 1
, where dp[i]
represents the minimum number of coins needed to make amount i
. Initialize all elements to Infinity
to indicate that no solution has been found yet, except for dp[0] = 0
, as zero coins are needed to make an amount of 0.coins
array in ascending order. This allows us to break the inner loop early when a coin is too large to contribute to the current amount, optimizing the computation.i
from 1 to amount
, iterate through each coin denomination in coins
:
i
is less than the coin value (i - coin < 0
), break the loop, as larger coins will also be too big (due to sorting).dp[i]
as the minimum of its current value and dp[i - coin] + 1
. This represents using the current coin (adding 1 to the coin count) plus the minimum number of coins needed for the remaining amount (i - coin
).dp
array, check dp[amount]
. If it is still Infinity
, it means no combination of coins can make the amount, so return -1. Otherwise, return dp[amount]
, which is the minimum number of coins needed.
The Coin Change problem is a classic example of dynamic programming in action. Given an array of distinct coin denominations and a target amount, the task is to determine the minimum number of coins needed to make up that amount. If the amount cannot be formed with any combination of coins, return -1. This problem tests optimization under constraints and is commonly used to evaluate recursion, memoization, and bottom-up DP skills.
A brute-force approach would recursively subtract each coin denomination from the total and attempt to solve the subproblem for the remaining amount. While intuitive, this leads to an enormous number of repeated calculations and redundant branches in the recursion tree.
To solve this problem optimally, we use a bottom-up DP approach that systematically builds solutions for smaller subproblems and uses them to compute the answer for larger amounts. Instead of recomputing, we store the minimum number of coins needed to form each amount from 0 to the target.
First, initialize a DP array dp
of size amount + 1
, where each element represents the minimum number of coins needed to make up that amount. We initialize all entries with a sentinel value like Infinity, except dp[0] = 0
(base case, zero coins needed for amount 0).
Then, iterate from 1 to the target amount. For each amount i
, loop through the coin denominations. If the coin is less than or equal to i
, update dp[i]
as the minimum between its current value and dp[i - coin] + 1
.
Finally, return dp[amount]
if it's not Infinity. If it remains Infinity, it means there's no way to make that amount with the given coins, so return -1.
The Coin Change problem elegantly demonstrates how a complex optimization problem can be efficiently solved using dynamic programming. With a bottom-up approach, you reduce redundant work and achieve scalable performance, making it ideal for large inputs and competitive programming environments. Mastering this problem builds a strong foundation for tackling other problems like Unbounded Knapsack and Minimum Path Sum.