|
1 | 1 | package _8_13_Stack_of_Boxes; |
2 | 2 |
|
3 | | -import java.util.ArrayDeque; |
4 | | -import java.util.HashMap; |
5 | | - |
6 | | -// Famous box stacking problem |
7 | | -// |
8 | | -// (Solution based off book's 2nd solution) |
9 | | -// |
10 | | -// Tips: |
11 | | -// - Have "Box" class with ".canBeAbove(Box other)" function. |
12 | | -// - Use HashMap<Box, ArrayDeque<Box>> to cache found solutions. Need to override .equals() and hashCode(). |
13 | | -// - Having "Box bottom" as a parameter is crucial but hard to think of. |
14 | | -// - Deep copy (which is what I did) or cloning (which is what book did) is needed to avoid insidious bugs. |
| 3 | +import java.util.*; |
| 4 | + |
| 5 | +// Algorithm |
| 6 | +// |
| 7 | +// 1. Create Box class with `.canBeAbove(Box other)` function. |
| 8 | +// 2. Sort Boxes in descending height order. |
| 9 | +// - This problem uses `<` instead of `<=` for Box heights. If we have 2 Boxes of equal height, |
| 10 | +// and [width, height, depth] of [4,4,4] and [7,4,7], it doesn't matter which Box comes first |
| 11 | +// after sorting, since both `Box`es can't be in the final solution. Box 1 cannot be on top of Box 2, |
| 12 | +// and Box 2 cannot be on top of Box 1. |
| 13 | +// 3. For each Box, you have 2 choices: |
| 14 | +// 1. Use the Box |
| 15 | +// 2. Don't use the Box. |
| 16 | +// 4. Recursively try both options. Return the larger height returned from these 2 options. |
| 17 | +// 5. Use a Map<Integer, Integer> cache to save solutions to sub-problems |
| 18 | +// 1. "key" is the index `i` in the list of boxes |
| 19 | +// 2. "value" is the max height possible from that Box to the end of our list of Boxes |
15 | 20 |
|
16 | 21 | public class StackOfBoxes { |
17 | | - public static ArrayDeque<Box> buildTallestStack(ArrayDeque<Box> boxes) { |
18 | | - return buildTallestStack(boxes, null, new HashMap<Box, ArrayDeque<Box>>()); |
19 | | - } |
20 | | - |
21 | | - public static ArrayDeque<Box> buildTallestStack(ArrayDeque<Box> boxes, Box bottom, HashMap<Box, ArrayDeque<Box>> cache) { |
22 | | - if (cache.containsKey(bottom)) { // checks to see if we already computed the solution |
23 | | - /* CRUCIAL to do a deep copy. The result we return is going to be altered, and we don't want the key of an entry |
24 | | - already placed in the HashMap to be altered. Without deep copy, we get incorrect solutions when testing */ |
25 | | - return deepCopy(cache.get(bottom)); |
26 | | - } |
27 | | - |
28 | | - int currHeight = 0; |
29 | | - int bestHeight = 0; |
30 | | - ArrayDeque<Box> currStack = new ArrayDeque<>(); |
31 | | - ArrayDeque<Box> bestStack = new ArrayDeque<>(); |
32 | | - |
33 | | - for (Box box : boxes) { |
34 | | - if (box.canPlaceAbove(bottom)) { |
35 | | - currStack = buildTallestStack(boxes, box, cache); |
36 | | - currHeight = stackHeight(currStack); |
37 | | - if (currHeight > bestHeight) { |
38 | | - bestHeight = currHeight; |
39 | | - bestStack = currStack; |
40 | | - } |
41 | | - } |
42 | | - } |
43 | | - |
44 | | - if (bottom != null) { |
45 | | - bestStack.addFirst(bottom); |
46 | | - cache.put(bottom, bestStack); |
| 22 | + public static int findMaxHeight(List<Box> boxes) { |
| 23 | + if (boxes == null || boxes.size() == 0) { |
| 24 | + return 0; |
47 | 25 | } |
48 | | - |
49 | | - return bestStack; |
| 26 | + Collections.sort(boxes, (box1, box2) -> box2.height - box1.height); // sort in descending height order |
| 27 | + return findMaxHeight(boxes, 0, null, new HashMap<>(boxes.size())); |
50 | 28 | } |
51 | | - |
52 | | - private static int stackHeight(ArrayDeque<Box> boxes) { |
53 | | - if (boxes == null) { |
| 29 | + |
| 30 | + private static int findMaxHeight(List<Box> boxes, int i, Box bottom, Map<Integer, Integer> cache) { |
| 31 | + if (i >= boxes.size()) { |
54 | 32 | return 0; |
| 33 | + } else if (cache.containsKey(i)) { |
| 34 | + return cache.get(i); |
55 | 35 | } |
56 | | - int height = 0; |
57 | | - for (Box box : boxes) { |
58 | | - height += box.height; |
59 | | - } |
60 | | - return height; |
61 | | - } |
62 | | - |
63 | | - private static ArrayDeque<Box> deepCopy(ArrayDeque<Box> boxes) { |
64 | | - ArrayDeque<Box> result = new ArrayDeque<>(); |
65 | | - for (Box box : boxes) { |
66 | | - result.add(new Box(box)); |
| 36 | + |
| 37 | + // height with this Box |
| 38 | + Box newBottom = boxes.get(i); |
| 39 | + int heightWith = 0; |
| 40 | + if (newBottom.canBeAbove(bottom)) { |
| 41 | + heightWith = newBottom.height + findMaxHeight(boxes, i + 1, newBottom, cache); |
67 | 42 | } |
68 | | - return result; |
| 43 | + |
| 44 | + // height without this Box |
| 45 | + int heightWithout = findMaxHeight(boxes, i + 1, bottom, cache); |
| 46 | + |
| 47 | + int max = Math.max(heightWith, heightWithout); |
| 48 | + cache.put(i, max); |
| 49 | + return max; |
69 | 50 | } |
70 | 51 | } |
| 52 | + |
| 53 | +// Time Complexity: Without caching, the recursive part is O(2^n) since there are `n` boxes, |
| 54 | +// and 2 options for each box (use or don't use). With caching, the recursive part is just O(n). |
| 55 | +// The total time complexity is therefore O(n log n) due to sorting. |
| 56 | +// Space Complexity: O(n) due to recursion. |
| 57 | + |
| 58 | +// - A Dynamic Programming Iterative solution (using an array instead of a HashMap) |
| 59 | +// is also possible. It will have the same Time/Space complexity as the above solution. |
0 commit comments