diff --git a/2021/README.md b/2021/README.md index 8b64c0baa1b7c8e54080afb22bfa4f38ae64ca70..52bb9049992e5bbf6f451dce949fc549071cdbf1 100644 --- a/2021/README.md +++ b/2021/README.md @@ -56,7 +56,7 @@ Part 2 looks a bit more interesting. We *could* go for an O(num_crabs * max_distance^2) solution, or we could recognize what Euler recognized: ∑_{i=1}^{n}i = n(n+1)/2. -### Day 8 +## Day 8 Seven-segment displays are cool. This has the potential to be a pain. Part 1 looks okay -- examining only the output patterns, count the number of patterns @@ -85,3 +85,32 @@ explanation, I'm going to use numerals to depict the sets of illuminated segment It turns out we can ascertain the digits without needing to record the segments. That's nifty. +## Day 9 + +Part 1 doesn't look too fancy I'll simplify the adjacency-check a little by +making the heightmap larger by two in each dimension so that I can create a +"frame" of `MAX_VALUE`s. + +Hmmm -- While that O(num_rows * num_columns) algorithm took a fraction of a +second, it was noticeable on the human scale. I don't think there's a better +big-O approach, but the constant factors could be in play if I were to look +for optimizations. + +Part 2 defines "basin" okay, but requires a little thought to express as +numbers. In the examples, the basins seem to be monotonic until reaching +height 9. Monotonic makes sense, given the definition "...flow downward to a +single low point," but what about a location of height 6 that has a basin on +either side? +- I'm going to assume, for now, that there are locations whose height is less + than 9 that are between two basins -- the simple check to see if there's an + adjacent location of greater height potentially could count a location as + being in two locations otherwise. If I'm wrong, I'll fix it. + - Come to think of it, plateaus would also be a problem for the simple + check. The examples don't have any plateaus. +- I'm going to change my part 1 code to create a height-9 frame. That's + enough for the low-point calculation and will simply part 2 a little. I'm + also going to change my part 1 code to record the low points so that I + don't have to re-calculate them for part 2. +- We'll go for a finite element approach. + +Hmm. Part 2 takes a few seconds, but no surprise there -- it's now O(n^5). diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day9.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day9.java new file mode 100644 index 0000000000000000000000000000000000000000..5204255d4912602d111e8f4ba1447194612bc764 --- /dev/null +++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day9.java @@ -0,0 +1,101 @@ +package edu.unl.cse.bohn.year2021; + +import edu.unl.cse.bohn.Puzzle; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +@SuppressWarnings("unused") +public class Day9 extends Puzzle { + public record Point(int x, int y) { + } + + private long[][] heightmap; + private boolean[][] lowPoints; + + public Day9(boolean isProductionReady) { + super(isProductionReady); + sampleData = """ + 2199943210 + 3987894921 + 9856789892 + 8767896789 + 9899965678"""; + } + + @Override + public long computePart1(List<String> data) { + heightmap = new long[data.size() + 2][data.get(0).length() + 2]; + lowPoints = new boolean[data.size() + 2][data.get(0).length() + 2]; + for (long[] row : heightmap) { + Arrays.fill(row, 9L); + } + for (boolean[] row : lowPoints) { + Arrays.fill(row, false); + } + for (int i = 0; i < data.size(); i++) { + String line = data.get(i); + for (int j = 0; j < line.length(); j++) { + heightmap[i + 1][j + 1] = Long.parseLong(String.valueOf(line.charAt(j))); + } + } + long riskTotal = 0L; + for (int i = 1; i < heightmap.length - 1; i++) { + for (int j = 1; j < heightmap[i].length - 1; j++) { + long height = heightmap[i][j]; + boolean isLowPoint = height < heightmap[i - 1][j] && + height < heightmap[i + 1][j] && + height < heightmap[i][j - 1] && + height < heightmap[i][j + 1]; + long risk = height + 1; + if (isLowPoint) { + lowPoints[i][j] = true; + riskTotal += risk; + } + } + } + return riskTotal; + } + + @Override + public long computePart2(List<String> data) { + List<Integer> basinSizes = new LinkedList<>(); + for (int i = 0; i < heightmap.length; i++) { + for (int j = 0; j < heightmap[i].length; j++) { + if (lowPoints[i][j]) { + Set<Point> basinPoints = new HashSet<>(); + basinPoints.add(new Point(i, j)); + boolean newPointsAdded = true; + while (newPointsAdded) { + newPointsAdded = false; + for (int m = 0; m < heightmap.length; m++) { + for (int n = 0; n < heightmap[m].length; n++) { + long height = heightmap[m][n]; + Point point = new Point(m, n); + if (!basinPoints.contains(point) && height < 9) { + for (Point neighbor : Set.of(new Point(m - 1, n), new Point(m + 1, n), + new Point(m, n - 1), new Point(m, n + 1))) { + if (basinPoints.contains(neighbor) && + height > heightmap[neighbor.x()][neighbor.y()]) { + basinPoints.add(point); + newPointsAdded = true; + } + } + } + } + } + } + basinSizes.add(basinPoints.size()); + } + } + } + basinSizes.sort(null); + int numberOfBasins = basinSizes.size(); + return (long)basinSizes.get(numberOfBasins - 1) * + (long)basinSizes.get(numberOfBasins - 2) * + (long)basinSizes.get(numberOfBasins - 3); + } +}