Skip to content

Commit e89dbe6

Browse files
William FisetWilliam Fiset
William Fiset
authored and
William Fiset
committed
Coin change slides
1 parent 2ab4bbd commit e89dbe6

File tree

3 files changed

+118
-33
lines changed

3 files changed

+118
-33
lines changed
172 KB
Binary file not shown.

src/main/java/com/williamfiset/algorithms/dp/CoinChange.java

+91-32
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,58 @@
33
* the minimum number of coins required for a certain amount of change given the coin denominations.
44
* You may use each coin denomination as many times as you please.
55
*
6-
* <p>Tested against: https://leetcode.com/problems/coin-change/
6+
* <p>Tested against: https://leetcode.com/problems/coin-change
77
*
8+
* Run locally:
9+
*
10+
* ./gradlew run -Palgorithm=dp.CoinChange
11+
*
812
* @author William Fiset, [email protected]
913
*/
1014
package com.williamfiset.algorithms.dp;
1115

16+
import java.util.ArrayList;
17+
import java.util.List;
18+
1219
public class CoinChange {
1320

21+
public static class Solution {
22+
int minCoins;
23+
List<Integer> selectedCoins = new ArrayList<Integer>();
24+
}
25+
1426
// TODO(william): setting an explicit infinity could lead to a wrong answer for
1527
// very large values. Prefer to use null instead.
1628
private static final int INF = Integer.MAX_VALUE / 2;
1729

18-
public static int coinChange(int[] coins, int amount) {
30+
private static void p(int[][] dp) {
31+
for (int[] r : dp) {
32+
for (int v : r) {
33+
System.out.printf("%4d, ", v == INF ? -1 : v);
34+
}
35+
System.out.println();
36+
}
37+
}
1938

39+
public static Solution coinChange(int[] coins, final int n) {
2040
if (coins == null) throw new IllegalArgumentException("Coins array is null");
2141
if (coins.length == 0) throw new IllegalArgumentException("No coin values :/");
42+
for (int coin : coins) {
43+
if (coin <= 0) {
44+
throw new IllegalArgumentException("Coin with value `" + coin + "` is not allowed.");
45+
}
46+
}
2247

23-
final int N = coins.length;
48+
final int m = coins.length;
2449
// Initialize table and set first row to be infinity
25-
int[][] dp = new int[N + 1][amount + 1];
50+
int[][] dp = new int[m + 1][n + 1];
2651
java.util.Arrays.fill(dp[0], INF);
2752
dp[1][0] = 0;
2853

2954
// Iterate through all the coins
30-
for (int i = 1; i <= N; i++) {
31-
55+
for (int i = 1; i <= m; i++) {
3256
int coinValue = coins[i - 1];
33-
for (int j = 1; j <= amount; j++) {
57+
for (int j = 1; j <= n; j++) {
3458

3559
// Consider not selecting this coin
3660
dp[i][j] = dp[i - 1][j];
@@ -42,23 +66,40 @@ public static int coinChange(int[] coins, int amount) {
4266
}
4367
}
4468

69+
// p(dp);
70+
71+
Solution solution = new Solution();
72+
4573
// The amount we wanted to make cannot be made :/
46-
if (dp[N][amount] == INF) return -1;
74+
if (dp[m][n] == INF) {
75+
solution.minCoins = -1;
76+
} else {
77+
solution.minCoins = dp[m][n];
78+
}
4779

48-
// Return the minimum number of coins needed
49-
return dp[N][amount];
50-
}
80+
for (int change = n, coinIndex = m; coinIndex > 0; ) {
81+
int coinValue = coins[coinIndex-1];
82+
boolean canSelectCoin = change - coinValue >= 0;
83+
if (canSelectCoin && dp[coinIndex][change - coinValue] < dp[coinIndex][change]) {
84+
solution.selectedCoins.add(coinValue);
85+
change -= coinValue;
86+
} else {
87+
coinIndex--;
88+
}
89+
}
5190

52-
public static int coinChangeSpaceEfficient(int[] coins, int amount) {
91+
return solution;
92+
}
5393

94+
public static int coinChangeSpaceEfficient(int[] coins, int n) {
5495
if (coins == null) throw new IllegalArgumentException("Coins array is null");
5596

5697
// Initialize table and set everything to infinity except first cell
57-
int[] dp = new int[amount + 1];
98+
int[] dp = new int[n + 1];
5899
java.util.Arrays.fill(dp, INF);
59100
dp[0] = 0;
60101

61-
for (int i = 1; i <= amount; i++) {
102+
for (int i = 1; i <= n; i++) {
62103
for (int coinValue : coins) {
63104
if (i - coinValue >= 0 && dp[i - coinValue] + 1 < dp[i]) {
64105
dp[i] = dp[i - coinValue] + 1;
@@ -67,48 +108,66 @@ public static int coinChangeSpaceEfficient(int[] coins, int amount) {
67108
}
68109

69110
// The amount we wanted to make cannot be made :/
70-
if (dp[amount] == INF) return -1;
111+
if (dp[n] == INF) return -1;
71112

72113
// Return the minimum number of coins needed
73-
return dp[amount];
114+
return dp[n];
74115
}
75116

76117
// The recursive approach has the advantage that it does not have to visit
77118
// all possible states like the tabular approach does. This can speedup
78119
// things especially if the coin denominations are large.
79-
public static int coinChangeRecursive(int[] coins, int amount) {
80-
120+
public static int coinChangeRecursive(int[] coins, int n) {
81121
if (coins == null) throw new IllegalArgumentException("Coins array is null");
82-
if (amount < 0) return -1;
122+
if (n < 0) return -1;
83123

84-
int[] dp = new int[amount + 1];
85-
return coinChangeRecursive(amount, coins, dp);
124+
int[] dp = new int[n + 1];
125+
return coinChangeRecursive(n, coins, dp);
86126
}
87127

88128
// Private helper method to actually go the recursion
89-
private static int coinChangeRecursive(int amount, int[] coins, int[] dp) {
90-
91-
// Base cases.
92-
if (amount < 0) return -1;
93-
if (amount == 0) return 0;
94-
if (dp[amount] != 0) return dp[amount];
129+
private static int coinChangeRecursive(int n, int[] coins, int[] dp) {
130+
if (n < 0) return -1;
131+
if (n == 0) return 0;
132+
if (dp[n] != 0) return dp[n];
95133

96134
int minCoins = INF;
97135
for (int coinValue : coins) {
98-
99-
int newAmount = amount - coinValue;
100-
int value = coinChangeRecursive(newAmount, coins, dp);
136+
int value = coinChangeRecursive(n - coinValue, coins, dp);
101137
if (value != -1 && value < minCoins) minCoins = value + 1;
102138
}
103139

104140
// If we weren't able to find some coins to make our
105141
// amount then cache -1 as the answer.
106-
return dp[amount] = (minCoins == INF) ? -1 : minCoins;
142+
return dp[n] = (minCoins == INF) ? -1 : minCoins;
107143
}
108144

109145
public static void main(String[] args) {
146+
// example1();
147+
// example2();
148+
example3();
149+
}
150+
151+
private static void example1() {
110152
int[] coins = {2, 6, 1};
111-
System.out.println(coinChange(coins, 17));
153+
System.out.println(coinChange(coins, 17).minCoins);
154+
System.out.println(coinChange(coins, 17).selectedCoins);
155+
System.out.println(coinChangeSpaceEfficient(coins, 17));
156+
System.out.println(coinChangeRecursive(coins, 17));
157+
}
158+
159+
private static void example2() {
160+
int[] coins = {2, 3, 5};
161+
System.out.println(coinChange(coins, 12).minCoins);
162+
System.out.println(coinChange(coins, 12).selectedCoins);
163+
System.out.println(coinChangeSpaceEfficient(coins, 12));
164+
System.out.println(coinChangeRecursive(coins, 12));
165+
}
166+
167+
private static void example3() {
168+
int[] coins = {3, 4, 7};
169+
System.out.println(coinChange(coins, 17).minCoins);
170+
System.out.println(coinChange(coins, 17).selectedCoins);
112171
System.out.println(coinChangeSpaceEfficient(coins, 17));
113172
System.out.println(coinChangeRecursive(coins, 17));
114173
}

src/test/java/com/williamfiset/algorithms/dp/CoinChangeTest.java

+27-1
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,38 @@ public void testCoinChange() {
1919

2020
int amount = TestUtils.randValue(1, 1000);
2121

22-
int v1 = CoinChange.coinChange(coinValues, amount);
22+
CoinChange.Solution solution1 = CoinChange.coinChange(coinValues, amount);
23+
int v1 = solution1.minCoins;
2324
int v2 = CoinChange.coinChangeSpaceEfficient(coinValues, amount);
2425
int v3 = CoinChange.coinChangeRecursive(coinValues, amount);
2526

2627
assertThat(v1).isEqualTo(v2);
2728
assertThat(v2).isEqualTo(v3);
2829
}
2930
}
31+
32+
@Test
33+
public void testCoinChangeSelectedCoins() {
34+
for (int i = 1; i < LOOPS; i++) {
35+
List<Integer> values = TestUtils.randomIntegerList(i, 1, 1000);
36+
int[] coinValues = Ints.toArray(values);
37+
38+
int amount = TestUtils.randValue(1, 1000);
39+
40+
CoinChange.Solution solution = CoinChange.coinChange(coinValues, amount);
41+
int selectedCoinsSum = 0;
42+
for (int v : solution.selectedCoins) {
43+
selectedCoinsSum += v;
44+
}
45+
if (solution.minCoins == -1) {
46+
assertThat(solution.selectedCoins.size()).isEqualTo(0);
47+
} else {
48+
// Verify that the size of the selected coins is equal to the optimal solution.
49+
assertThat(solution.selectedCoins.size()).isEqualTo(solution.minCoins);
50+
51+
// Further verify that the sum of the selected coins equals the amount we want to make.
52+
assertThat(selectedCoinsSum).isEqualTo(amount);
53+
}
54+
}
55+
}
3056
}

0 commit comments

Comments
 (0)