Problem Statement
Given an array coins representing different denominations of coins and an integer amount, return the fewest number of coins needed to make up that amount. If it is impossible to make the amount, return -1.
Constraints:
1 <= coins.length <= 121 <= coins[i] <= 2^31 - 10 <= amount <= 10^4
Optimal Approach - Dynamic Programming (Bottom-Up)
The best way to solve this problem efficiently is using Dynamic Programming (DP) with a bottom-up approach.
Explanation:
- Create a
dparray of sizeamount + 1, initialized toamount + 1(an impossible high value) exceptdp[0] = 0. - Iterate over each
coinincoins, updatingdp[j] = min(dp[j], dp[j - coin] + 1)for everyjfromcointoamount. - If
dp[amount]remains unchanged (amount + 1), return-1, else returndp[amount].
C++ Code (LeetCode-Compatible)
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount + 1, amount + 1);
dp[0] = 0;
for (int coin : coins) {
for (int j = coin; j <= amount; ++j) {
dp[j] = min(dp[j], dp[j - coin] + 1);
}
}
return dp[amount] == amount + 1 ? -1 : dp[amount];
}
};
Step-by-Step Explanation
-
Initialization:
- We create a
dparray of sizeamount + 1and set it toamount + 1(a large number, as an impossible value). dp[0] = 0because 0 coins are needed to make an amount of 0.
- We create a
-
Iterate Over Each Coin:
- For each coin, iterate over all amounts
jfromcointoamount. - Update
dp[j]by checking if using the current coin reduces the number of coins required.
- For each coin, iterate over all amounts
-
Return Result:
- If
dp[amount]remains unchanged, return-1(not possible to form the amount), otherwise returndp[amount].
- If
Dry Run / Execution Steps
Example:
Input: coins = [1, 2, 5], amount = 11
DP Table Calculation:
dp[0] = 0 (Base case)
dp[1] = 1 (1 coin of 1)
dp[2] = 1 (1 coin of 2)
dp[3] = 2 (1+2)
dp[4] = 2 (2+2)
dp[5] = 1 (1 coin of 5)
dp[6] = 2 (1+5 or 2+2+2)
dp[7] = 2 (2+5)
dp[8] = 3 (3 coins of 2 or 1+2+5)
dp[9] = 3 (2+2+5)
dp[10] = 2 (5+5)
dp[11] = 3 (1+5+5)
Output: 3
Alternative Approaches & Why Not?
| Approach | Time Complexity | Space Complexity | Why Not? |
|---|---|---|---|
| Brute Force (Recursion) | Exponential | O(n) | Slow for large inputs |
| Greedy Approach | Undefined (Fails in some cases) | O(1) | Doesn't work for cases like coins=[3,7] and amount=14 |
| Dynamic Programming (Top-Down with Memoization) | O(n*amount) | O(n+amount) | Similar efficiency but requires extra recursion overhead |
Best Solution & Why It’s Best
The bottom-up DP approach is the most efficient because:
- It avoids recursion overhead.
- It has a predictable time complexity of
O(n*amount). - It guarantees the optimal solution.
Complexity Analysis
- Time Complexity:
O(n * amount)(wherenis the number of coins) - Space Complexity:
O(amount)(for storingdparray)
Conclusion
- The best approach is Bottom-Up Dynamic Programming.
- Alternative approaches like recursion or greedy fail in some cases.
- Recommended practice problems:
- Minimum Coins to Make Change
- Unbounded Knapsack Problem
- Combination Sum (LeetCode 39)