-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathMakingChange.java
164 lines (144 loc) · 5.72 KB
/
MakingChange.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
* Title: Making change
* Author: Sam Gavis-Hughson
* Date: 08/01/2017
*
* Given an integer representing a given amount of change, write a function to
* compute the total number of coins required to make that amount of change.
*
* eg. (assuming American coins: 1, 5, 10, and 25 cents)
* minCoins(1) = 1 (1)
* minCoins(6) = 2 (5 + 1)
* minCoins(49) = 7 (25 + 10 + 10 + 1 + 1 + 1 + 1)
*
* Execution: javac MakeChange.java && java MakeChange
*/
// We will assume that there is always a 1¢ coin available, so there is always
// a valid way to make the amount of change. We will also implement this as an
// object that is instantiated with a set of coin sizes so we don't have to
// pass it into our function every time
public class MakingChange {
private int[] coins;
// Constructor
public MakingChange(int[] coins) {
this.coins = coins;
}
// Greedy algorithm. Take the largest coin possible each time. Doesn't work
// for all coin systems. We also assume that the coins are in descending
// order
public int greedyMakingChange(int c) {
int totalCoins = 0;
// Remove the largest coin from c that doesn't make c negative
for (int coin : coins) {
while (c - coin >= 0) {
totalCoins++;
c -= coin;
}
}
return totalCoins;
}
// Optimized greedy algorithm. By using mods, we can solve this in constant
// time but this solution has the same limitations
public int optimalGreedyMakingChange(int c) {
int totalCoins = 0;
// Find how many of each coin can go into the remaining total
for (int coin : coins) {
totalCoins += c / coin;
c %= coin;
}
return totalCoins;
}
// Brute force solution. Go through every possible combination of coins that
// sum up to c to find the minimum number
public int naiveMakingChange(int c) {
if (c == 0) return 0;
int minCoins = Integer.MAX_VALUE;
// Try removing each coin from the total and see how many more coins
// are required
for (int coin : coins) {
// Skip a coin if it's value is greater than the amount remaining
if (c - coin >= 0) {
int currMinCoins = naiveMakingChange(c - coin);
if (currMinCoins < minCoins) minCoins = currMinCoins;
}
}
// Our recursive call removes one coin from the amount, so add it back
return minCoins + 1;
}
// Top down dynamic solution. Cache the values as we compute them
public int topDownMakingChangeOptimized(int c) {
// Initialize cache with values as -1
int[] cache = new int[c + 1];
for (int i = 1; i < c + 1; i++) cache[i] = -1;
return topDownMakingChangeOptimized(c, cache);
}
// Overloaded recursive function
private int topDownMakingChangeOptimized(int c, int[] cache) {
// Return the value if it's in the cache
if (cache[c] >= 0) return cache[c];
int minCoins = Integer.MAX_VALUE;
// Try each different coin to see which is best
for (int coin : coins) {
if (c - coin >= 0) {
int currMinCoins = topDownMakingChangeOptimized(c - coin, cache);
if (currMinCoins < minCoins) minCoins = currMinCoins;
}
}
// Save the value into the cache
cache[c] = minCoins + 1;
return cache[c];
}
// Bottom up dynamic programming solution. Iteratively compute number of
// coins for larger and larger amounts of change
public int bottomUpMakingChange(int c) {
int[] cache = new int[c + 1];
for (int i = 1; i <= c; i++) {
int minCoins = Integer.MAX_VALUE;
// Try removing each coin from the total and see which requires
// the fewest additional coins
for (int coin : coins) {
if (i - coin >= 0) {
int currCoins = cache[i-coin] + 1;
if (currCoins < minCoins) {
minCoins = currCoins;
}
}
}
cache[i] = minCoins;
}
return cache[c];
}
// Sample testcases
public static void main(String[] args) {
int[] americanCoins = new int[]{25, 10, 5, 1};
int[] randomCoins = new int[]{10, 6, 1};
(new TestCase(americanCoins, 1, 1)).run();
(new TestCase(americanCoins, 6, 2)).run();
(new TestCase(americanCoins, 47, 5)).run();
(new TestCase(randomCoins, 1, 1)).run();
(new TestCase(randomCoins, 8, 3)).run();
(new TestCase(randomCoins, 11, 2)).run();
(new TestCase(randomCoins, 12, 2)).run();
System.out.println("Passed all test cases");
}
// Class for defining and running test cases
private static class TestCase {
private int[] coins;
private int input;
private int output;
private TestCase(int[] coins, int input, int output) {
this.coins = coins;
this.input = input;
this.output = output;
}
private void run() {
MakingChange mc = new MakingChange(coins);
assert mc.naiveMakingChange(input) == output:
"naiveMakingChange failed for input = " + input;
assert mc.topDownMakingChangeOptimized(input) == output:
"topDownMakingChangeOptimized failed for input = " + input;
assert mc.bottomUpMakingChange(input) == output:
"bottomUpMakingChange failed for input = " + input;
}
}
}