Skip to content

Commit 58d0962

Browse files
William FisetWilliam Fiset
authored andcommitted
Coin change
1 parent d9d9d54 commit 58d0962

File tree

3 files changed

+82
-25
lines changed

3 files changed

+82
-25
lines changed
300 KB
Binary file not shown.

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

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,22 @@
1515

1616
import java.util.ArrayList;
1717
import java.util.List;
18+
import java.util.Optional;
1819

1920
public class CoinChange {
2021

2122
public static class Solution {
22-
int minCoins;
23+
// Contains the minimum number of coins to make a certain amount, if an optimal solution exists.
24+
Optional<Integer> minCoins = Optional.empty();
25+
26+
// The coins selected as part of the optimal solution.
2327
List<Integer> selectedCoins = new ArrayList<Integer>();
2428
}
2529

2630
// TODO(william): setting an explicit infinity could lead to a wrong answer for
2731
// very large values. Prefer to use null instead.
2832
private static final int INF = Integer.MAX_VALUE / 2;
2933

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-
}
38-
3934
public static Solution coinChange(int[] coins, final int n) {
4035
if (coins == null) throw new IllegalArgumentException("Coins array is null");
4136
if (coins.length == 0) throw new IllegalArgumentException("No coin values :/");
@@ -70,11 +65,10 @@ public static Solution coinChange(int[] coins, final int n) {
7065

7166
Solution solution = new Solution();
7267

73-
// The amount we wanted to make cannot be made :/
74-
if (dp[m][n] == INF) {
75-
solution.minCoins = -1;
68+
if (dp[m][n] != INF) {
69+
solution.minCoins = Optional.of(dp[m][n]);
7670
} else {
77-
solution.minCoins = dp[m][n];
71+
return solution;
7872
}
7973

8074
for (int change = n, coinIndex = m; coinIndex > 0; ) {
@@ -91,7 +85,7 @@ public static Solution coinChange(int[] coins, final int n) {
9185
return solution;
9286
}
9387

94-
public static int coinChangeSpaceEfficient(int[] coins, int n) {
88+
public static Solution coinChangeSpaceEfficient(int[] coins, int n) {
9589
if (coins == null) throw new IllegalArgumentException("Coins array is null");
9690

9791
// Initialize table and set everything to infinity except first cell
@@ -107,11 +101,31 @@ public static int coinChangeSpaceEfficient(int[] coins, int n) {
107101
}
108102
}
109103

110-
// The amount we wanted to make cannot be made :/
111-
if (dp[n] == INF) return -1;
104+
Solution solution = new Solution();
105+
if (dp[n] != INF) {
106+
solution.minCoins = Optional.of(dp[n]);
107+
} else {
108+
return solution;
109+
}
110+
111+
for (int i = n; i > 0; ) {
112+
int selectedCoinValue = INF;
113+
int cellWithFewestCoins = dp[i];
114+
for (int coin : coins) {
115+
if (i - coin < 0) {
116+
continue;
117+
}
118+
if (dp[i - coin] < cellWithFewestCoins) {
119+
cellWithFewestCoins = dp[i - coin];
120+
selectedCoinValue = coin;
121+
}
122+
}
123+
solution.selectedCoins.add(selectedCoinValue);
124+
i -= selectedCoinValue;
125+
}
112126

113127
// Return the minimum number of coins needed
114-
return dp[n];
128+
return solution;
115129
}
116130

117131
// The recursive approach has the advantage that it does not have to visit
@@ -142,6 +156,23 @@ private static int coinChangeRecursive(int n, int[] coins, int[] dp) {
142156
return dp[n] = (minCoins == INF) ? -1 : minCoins;
143157
}
144158

159+
// DP table print function. Used for debugging.
160+
private static void p(int[][] dp) {
161+
for (int[] r : dp) {
162+
for (int v : r) {
163+
System.out.printf("%4d, ", v == INF ? -1 : v);
164+
}
165+
System.out.println();
166+
}
167+
}
168+
169+
private static void p(int[] dp) {
170+
for (int v : dp) {
171+
System.out.printf("%4d, ", v == INF ? -1 : v);
172+
}
173+
System.out.println();
174+
}
175+
145176
public static void main(String[] args) {
146177
// example1();
147178
// example2();
@@ -152,10 +183,10 @@ public static void main(String[] args) {
152183
private static void example4() {
153184
int n = 11;
154185
int[] coins = {2, 4, 1};
155-
System.out.println(coinChange(coins, n).minCoins);
186+
// System.out.println(coinChange(coins, n).minCoins);
156187
System.out.println(coinChangeSpaceEfficient(coins, n));
157-
System.out.println(coinChangeRecursive(coins, n));
158-
System.out.println(coinChange(coins, n).selectedCoins);
188+
// System.out.println(coinChangeRecursive(coins, n));
189+
// System.out.println(coinChange(coins, n).selectedCoins);
159190
}
160191

161192
private static void example1() {

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

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ public void testCoinChange() {
2020
int amount = TestUtils.randValue(1, 1000);
2121

2222
CoinChange.Solution solution1 = CoinChange.coinChange(coinValues, amount);
23-
int v1 = solution1.minCoins;
24-
int v2 = CoinChange.coinChangeSpaceEfficient(coinValues, amount);
23+
CoinChange.Solution solution2 = CoinChange.coinChangeSpaceEfficient(coinValues, amount);
24+
int v1 = solution1.minCoins.isPresent() ? solution1.minCoins.get() : -1;
25+
int v2 = solution2.minCoins.isPresent() ? solution2.minCoins.get() : -1;
2526
int v3 = CoinChange.coinChangeRecursive(coinValues, amount);
2627

2728
assertThat(v1).isEqualTo(v2);
@@ -42,11 +43,36 @@ public void testCoinChangeSelectedCoins() {
4243
for (int v : solution.selectedCoins) {
4344
selectedCoinsSum += v;
4445
}
45-
if (solution.minCoins == -1) {
46+
if (!solution.minCoins.isPresent()) {
4647
assertThat(solution.selectedCoins.size()).isEqualTo(0);
4748
} else {
4849
// Verify that the size of the selected coins is equal to the optimal solution.
49-
assertThat(solution.selectedCoins.size()).isEqualTo(solution.minCoins);
50+
assertThat(solution.selectedCoins.size()).isEqualTo(solution.minCoins.get());
51+
52+
// Further verify that the sum of the selected coins equals the amount we want to make.
53+
assertThat(selectedCoinsSum).isEqualTo(amount);
54+
}
55+
}
56+
}
57+
58+
@Test
59+
public void testCoinChangeSpaceEfficientSelectedCoins() {
60+
for (int i = 1; i < LOOPS; i++) {
61+
List<Integer> values = TestUtils.randomIntegerList(i, 1, 1000);
62+
int[] coinValues = Ints.toArray(values);
63+
64+
int amount = TestUtils.randValue(1, 1000);
65+
66+
CoinChange.Solution solution = CoinChange.coinChangeSpaceEfficient(coinValues, amount);
67+
int selectedCoinsSum = 0;
68+
for (int v : solution.selectedCoins) {
69+
selectedCoinsSum += v;
70+
}
71+
if (!solution.minCoins.isPresent()) {
72+
assertThat(solution.selectedCoins.size()).isEqualTo(0);
73+
} else {
74+
// Verify that the size of the selected coins is equal to the optimal solution.
75+
assertThat(solution.selectedCoins.size()).isEqualTo(solution.minCoins.get());
5076

5177
// Further verify that the sum of the selected coins equals the amount we want to make.
5278
assertThat(selectedCoinsSum).isEqualTo(amount);

0 commit comments

Comments
 (0)