import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.stream.Stream; import java.util.stream.StreamSupport; public class SkyScrapers { private static class Permutations> implements Iterable> { public class PermutationsIterator implements Iterator> { private List elements; private boolean _hasNext; private boolean firstIteration = true; public PermutationsIterator(List elements) { this.elements = elements; _hasNext = elements.size() > 0; } private void swap(int k, int l) { T tmp = elements.get(k); elements.set(k, elements.get(l)); elements.set(l, tmp); } public boolean hasNext() { return _hasNext; } public List next() { if (!_hasNext) { throw new NoSuchElementException("No more permutations are left"); } var lastIteration = new ArrayList(elements); int k = 0, l = 0; _hasNext = false; for (int i = elements.size() - 1; i > 0; i--) { if (elements.get(i).compareTo(elements.get(i - 1)) > 0) { k = i - 1; _hasNext = true; break; } } for (int i = elements.size() - 1; i > k; i--) { if (elements.get(i).compareTo(elements.get(k)) > 0) { l = i; break; } } swap(k, l); Collections.reverse(elements.subList(k + 1, elements.size())); return lastIteration; } } private List elements; public Permutations(List elements) { this.elements = elements; Collections.sort(this.elements); } public Iterator> iterator() { return new PermutationsIterator(elements); } public Stream> stream() { return StreamSupport.stream(this.spliterator(), false); } } private static class PossibleConfigurations implements Iterable> { private List initial; private int clue; private void generateInitial(int size) { initial = new ArrayList(); for (int i = 0; i < size; i++) { initial.add(i + 1); } } public PossibleConfigurations(int size, int clue) { generateInitial(size); this.clue = clue; } public Iterator> iterator() { if (clue == 0) { return (new Permutations(initial)).iterator(); } return (new Permutations(initial)) .stream() .filter(heights -> isValid((List) heights, clue)) .iterator(); } } private static boolean isValid(List heights, int clue) { int canSee = 1; int lastHeight = heights.get(0); for (int i = 1; i < heights.size(); i++) { int currentHeight = heights.get(i); if (currentHeight > lastHeight) { lastHeight = currentHeight; canSee++; } } return canSee == clue; } private static int getDx(int clueIndex) { if (clueIndex >= 4 && clueIndex <= 7) { return -1; } if (clueIndex >= 12) { return 1; } return 0; } private static int getDy(int clueIndex) { if (clueIndex <= 3) { return 1; } if (clueIndex >= 8 && clueIndex <= 11) { return -1; } return 0; } private static int getStartX(int clueIndex) { if (clueIndex < 4) { return clueIndex; } if (clueIndex <= 7) { return 3; } if (clueIndex <= 11) { return 3 - clueIndex % 4; } return 0; } private static int getStartY(int clueIndex) { if (clueIndex < 4) { return 0; } if (clueIndex <= 7) { return clueIndex % 4; } if (clueIndex <= 11) { return 3; } return 3 - clueIndex % 4; } private static List backupHeights(int[][] heights, int clueIndex) { List backup = new ArrayList(); int dx = getDx(clueIndex); int dy = getDy(clueIndex); for ( int x = getStartX(clueIndex), y = getStartY(clueIndex); x >= 0 && x < 4 && y >= 0 && y < 4; x += dx, y += dy ) { backup.add(heights[y][x]); } return backup; } private static boolean emplaceHeights( int[][] heights, int clueIndex, List newHeights, boolean force ) { int i = 0; int dx = getDx(clueIndex); int dy = getDy(clueIndex); for ( int x = getStartX(clueIndex), y = getStartY(clueIndex); x >= 0 && x < 4 && y >= 0 && y < 4; x += dx, y += dy ) { if ( !force && heights[y][x] != 0 && heights[y][x] != newHeights.get(i) ) { return false; } heights[y][x] = newHeights.get(i++); } return true; } private static boolean solvePuzzle( int[][] heights, int[] clues, int clueIndex, boolean ignoreZeroes ) { while (clueIndex < 16 && ((ignoreZeroes && clues[clueIndex] == 0) || (!ignoreZeroes && clues[clueIndex] != 0))) { clueIndex++; } if (clueIndex >= 16) { return true; } // create copy of heights to ensure correct resetting List currentHeights = backupHeights(heights, clueIndex); // iterate through the options for (List possibleHeights : new PossibleConfigurations(4, clues[clueIndex])) { // emplace heights and if conflict occurs, reset and try next one if (!emplaceHeights(heights, clueIndex, possibleHeights, false)) { emplaceHeights(heights, clueIndex, currentHeights, true); continue; } // if no conflict present, try filling out other clues if (solvePuzzle(heights, clues, clueIndex + 1, ignoreZeroes)) { return true; } // otherwise reset heights and try again emplaceHeights(heights, clueIndex, currentHeights, true); } // if we got here, there is no feasible configuration of buildings return false; } static int[][] solvePuzzle(int[] clues) { var result = new int[4][4]; solvePuzzle(result, clues, 0, true); // in case there are left zeroes solvePuzzle(result, clues, 0, false); return result; } }