You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.
Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
You may assume that you have an infinite number of each kind of coin.
Input: coins = [1,2,5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1
Input: coins = [2], amount = 3
Output: -1
Explanation: Cannot make amount 3 with only coins of denomination 2.
Input: coins = [1], amount = 0
Output: 0
Explanation: No coins needed for amount 0.
1 ≤ coins.length ≤ 121 ≤ coins[i] ≤ 2^31 - 10 ≤ amount ≤ 10^4This is an Unbounded Knapsack problem!
For each amount, we try every coin and take the minimum:
Build the solution from amount 0 to target.
Treat it as a shortest path problem!
For coins = [1, 2, 5] and amount = 11:
dp[0] = 0 (base case)
dp[1] = 1 (1 coin: 1)
dp[2] = 1 (1 coin: 2)
dp[3] = 2 (2 coins: 2+1)
dp[4] = 2 (2 coins: 2+2)
dp[5] = 1 (1 coin: 5)
dp[6] = 2 (2 coins: 5+1)
dp[7] = 2 (2 coins: 5+2)
dp[8] = 3 (3 coins: 5+2+1)
dp[9] = 3 (3 coins: 5+2+2)
dp[10] = 2 (2 coins: 5+5)
dp[11] = 3 (3 coins: 5+5+1)
This problem demonstrates:
1import java.util.*;
2
3class Solution {
4 // Solution 1: Dynamic Programming (Bottom-Up)
5 public int coinChange(int[] coins, int amount) {
6 // Create DP array with infinity
7 int[] dp = new int[amount + 1];
8 Arrays.fill(dp, amount + 1); // Use amount+1 as "infinity"
9 dp[0] = 0; // Base case: 0 coins for amount 0
10
11 // For each amount from 1 to target
12 for (int i = 1; i <= amount; i++) {
13 // Try each coin
14 for (int coin : coins) {
15 if (coin <= i) {
16 // Update minimum coins needed
17 dp[i] = Math.min(dp[i], dp[i - coin] + 1);
18 }
19 }
20 }
21
22 // Return result or -1 if impossible
23 return dp[amount] > amount ? -1 : dp[amount];
24 }
25
26 // Solution 2: BFS Approach (Shortest Path)
27 public int coinChangeBFS(int[] coins, int amount) {
28 if (amount == 0) return 0;
29
30 // BFS queue: stores current amounts
31 Queue<Integer> queue = new LinkedList<>();
32 Set<Integer> visited = new HashSet<>();
33 queue.offer(0);
34 visited.add(0);
35
36 int numCoins = 0;
37
38 while (!queue.isEmpty()) {
39 int size = queue.size();
40 numCoins++;
41
42 for (int i = 0; i < size; i++) {
43 int currAmount = queue.poll();
44
45 // Try adding each coin
46 for (int coin : coins) {
47 int nextAmount = currAmount + coin;
48
49 // Found the target
50 if (nextAmount == amount) {
51 return numCoins;
52 }
53
54 // Continue BFS if not visited and within bounds
55 if (nextAmount < amount && !visited.contains(nextAmount)) {
56 visited.add(nextAmount);
57 queue.offer(nextAmount);
58 }
59 }
60 }
61 }
62
63 return -1; // Cannot make the amount
64 }
65
66 // Solution 3: Top-Down DP with Memoization
67 private Map<Integer, Integer> memo = new HashMap<>();
68
69 public int coinChangeMemo(int[] coins, int amount) {
70 int result = dp(coins, amount);
71 return result == Integer.MAX_VALUE ? -1 : result;
72 }
73
74 private int dp(int[] coins, int remaining) {
75 // Base cases
76 if (remaining == 0) return 0;
77 if (remaining < 0) return Integer.MAX_VALUE;
78
79 // Check memo
80 if (memo.containsKey(remaining)) {
81 return memo.get(remaining);
82 }
83
84 // Try each coin and take minimum
85 int minCoins = Integer.MAX_VALUE;
86 for (int coin : coins) {
87 int result = dp(coins, remaining - coin);
88 if (result != Integer.MAX_VALUE) {
89 minCoins = Math.min(minCoins, result + 1);
90 }
91 }
92
93 memo.put(remaining, minCoins);
94 return minCoins;
95 }
96}
97