3
3
* the minimum number of coins required for a certain amount of change given the coin denominations.
4
4
* You may use each coin denomination as many times as you please.
5
5
*
6
- * <p>Tested against: https://leetcode.com/problems/coin-change/
6
+ * <p>Tested against: https://leetcode.com/problems/coin-change
7
7
*
8
+ * Run locally:
9
+ *
10
+ * ./gradlew run -Palgorithm=dp.CoinChange
11
+ *
8
12
* @author William Fiset, [email protected]
9
13
*/
10
14
package com .williamfiset .algorithms .dp ;
11
15
16
+ import java .util .ArrayList ;
17
+ import java .util .List ;
18
+
12
19
public class CoinChange {
13
20
21
+ public static class Solution {
22
+ int minCoins ;
23
+ List <Integer > selectedCoins = new ArrayList <Integer >();
24
+ }
25
+
14
26
// TODO(william): setting an explicit infinity could lead to a wrong answer for
15
27
// very large values. Prefer to use null instead.
16
28
private static final int INF = Integer .MAX_VALUE / 2 ;
17
29
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
+ }
19
38
39
+ public static Solution coinChange (int [] coins , final int n ) {
20
40
if (coins == null ) throw new IllegalArgumentException ("Coins array is null" );
21
41
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
+ }
22
47
23
- final int N = coins .length ;
48
+ final int m = coins .length ;
24
49
// 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 ];
26
51
java .util .Arrays .fill (dp [0 ], INF );
27
52
dp [1 ][0 ] = 0 ;
28
53
29
54
// Iterate through all the coins
30
- for (int i = 1 ; i <= N ; i ++) {
31
-
55
+ for (int i = 1 ; i <= m ; i ++) {
32
56
int coinValue = coins [i - 1 ];
33
- for (int j = 1 ; j <= amount ; j ++) {
57
+ for (int j = 1 ; j <= n ; j ++) {
34
58
35
59
// Consider not selecting this coin
36
60
dp [i ][j ] = dp [i - 1 ][j ];
@@ -42,23 +66,40 @@ public static int coinChange(int[] coins, int amount) {
42
66
}
43
67
}
44
68
69
+ // p(dp);
70
+
71
+ Solution solution = new Solution ();
72
+
45
73
// 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
+ }
47
79
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
+ }
51
90
52
- public static int coinChangeSpaceEfficient (int [] coins , int amount ) {
91
+ return solution ;
92
+ }
53
93
94
+ public static int coinChangeSpaceEfficient (int [] coins , int n ) {
54
95
if (coins == null ) throw new IllegalArgumentException ("Coins array is null" );
55
96
56
97
// Initialize table and set everything to infinity except first cell
57
- int [] dp = new int [amount + 1 ];
98
+ int [] dp = new int [n + 1 ];
58
99
java .util .Arrays .fill (dp , INF );
59
100
dp [0 ] = 0 ;
60
101
61
- for (int i = 1 ; i <= amount ; i ++) {
102
+ for (int i = 1 ; i <= n ; i ++) {
62
103
for (int coinValue : coins ) {
63
104
if (i - coinValue >= 0 && dp [i - coinValue ] + 1 < dp [i ]) {
64
105
dp [i ] = dp [i - coinValue ] + 1 ;
@@ -67,48 +108,66 @@ public static int coinChangeSpaceEfficient(int[] coins, int amount) {
67
108
}
68
109
69
110
// The amount we wanted to make cannot be made :/
70
- if (dp [amount ] == INF ) return -1 ;
111
+ if (dp [n ] == INF ) return -1 ;
71
112
72
113
// Return the minimum number of coins needed
73
- return dp [amount ];
114
+ return dp [n ];
74
115
}
75
116
76
117
// The recursive approach has the advantage that it does not have to visit
77
118
// all possible states like the tabular approach does. This can speedup
78
119
// 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 ) {
81
121
if (coins == null ) throw new IllegalArgumentException ("Coins array is null" );
82
- if (amount < 0 ) return -1 ;
122
+ if (n < 0 ) return -1 ;
83
123
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 );
86
126
}
87
127
88
128
// 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 ];
95
133
96
134
int minCoins = INF ;
97
135
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 );
101
137
if (value != -1 && value < minCoins ) minCoins = value + 1 ;
102
138
}
103
139
104
140
// If we weren't able to find some coins to make our
105
141
// amount then cache -1 as the answer.
106
- return dp [amount ] = (minCoins == INF ) ? -1 : minCoins ;
142
+ return dp [n ] = (minCoins == INF ) ? -1 : minCoins ;
107
143
}
108
144
109
145
public static void main (String [] args ) {
146
+ // example1();
147
+ // example2();
148
+ example3 ();
149
+ }
150
+
151
+ private static void example1 () {
110
152
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 );
112
171
System .out .println (coinChangeSpaceEfficient (coins , 17 ));
113
172
System .out .println (coinChangeRecursive (coins , 17 ));
114
173
}
0 commit comments