Chapter 3
Skyscrapers
Skyscrapers turns a Latin square into an aerial photograph. Each cell of an grid holds the height of a building, a digit between and ; each row and each column is a permutation of those heights. The puzzle’s twist, new to this book, is visibility. Numbers written outside the grid along each edge record how many of the buildings in the corresponding row or column would be visible to an observer standing at that edge, with a taller building hiding every shorter one behind it.
The puzzle is sometimes attributed to Nikoli and sometimes to the broader tradition of Japanese visibility puzzles; an early form circulated at the World Puzzle Championship in the s. The cleanest way to treat it is as a constraint-satisfaction problem built on three layers: a Latin square at the base, and then, for each edge clue, a counting constraint expressed through a running maximum over the corresponding row or column.
Rules and a small instance
A Skyscrapers puzzle is an grid. Each cell holds a digit from to . Along any of the four sides of the grid, a subset of row- or column-positions carry a visibility clue: a positive integer.
Each row and each column contains every digit from to exactly once.
For any visibility clue placed at the edge of a row (or column), exactly cells of that row (column), read from the clue edge inward, are visible: not blocked by a strictly taller cell closer to the edge.
Figure 3.1 gives a Skyscrapers puzzle with ten edge clues. The two west-edge clues sit on rows and ; the two east-edge clues on rows and ; the north and south edges carry three and three clues respectively.
The programming model
Let denote the height in cell . The Latin-square layer is the usual pair of all-different families:
The visibility layer needs one new idea. For a given line of cells read from a clue edge inward, define running max and . Cell is visible from the edge exactly when . The visibility count is and the clue on that edge imposes .
In CP-SAT, enters the model as an auxiliary integer variable tied to the previous and the current cell by a max constraint; is a boolean reified against the strict inequality. Each clue therefore adds auxiliary integer variables, booleans, and a single linear sum equation.
A solver in thirty-five lines
from ortools.sat.python import cp_model
def solve_skyscrapers(n, clues):
"""Solve an n x n Skyscrapers puzzle.
clues : iterable of (side, idx, count) with
side in {'N','E','S','W'}, idx 1-based.
"""
m = cp_model.CpModel()
x = [[m.NewIntVar(1, n, f"x{r}{c}")
for c in range(n)] for r in range(n)]
for r in range(n):
m.AddAllDifferent(x[r])
for c in range(n):
m.AddAllDifferent([x[r][c] for r in range(n)])
def add_vis(line, count):
running = m.NewIntVar(0, n, "")
m.Add(running == 0)
vis = []
for v in line:
is_vis = m.NewBoolVar("")
m.Add(v > running).OnlyEnforceIf(is_vis)
m.Add(v <= running).OnlyEnforceIf(is_vis.Not())
vis.append(is_vis)
nm = m.NewIntVar(0, n, "")
m.AddMaxEquality(nm, [running, v])
running = nm
m.Add(sum(vis) == count)
for side, idx, cnt in clues:
row = x[idx-1]
col = [x[r][idx-1] for r in range(n)]
if side == 'W': add_vis(row, cnt)
elif side == 'E': add_vis(row[::-1], cnt)
elif side == 'N': add_vis(col, cnt)
elif side == 'S': add_vis(col[::-1], cnt)
s = cp_model.CpSolver()
ok = s.Solve(m)
if ok not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
raise RuntimeError("no solution")
return [[s.Value(x[r][c])
for c in range(n)] for r in range(n)]
The instance of Figure 3.1 solves in about milliseconds and admits a single completion; the solver returns Figure 3.2.
A hard instance
Figure 3.3 is Mikhail Khotiner’s Puzzle from the janko.at Wolkenkratzer archive, graded there as the single hardest puzzle in the collection. The grid is , with four edge clues missing, one interior given digit at , and visibility counts that force long chains of deduction. CP-SAT returns the unique completion (Figure 3.4) in about milliseconds.
What the running-max trick buys
The visibility count is non-convex in the raw heights; a naive formulation would enumerate the permutations of each row and column, filtering by the visibility count. For that is permutations per line, tractable but ugly. The running-max reification swaps the enumeration for a chain of auxiliary variables per clue; the solver’s propagation then stays local. Crucially, the reification is tight: if is forced true by domain reasoning, the booleans update without any search; if the strict inequality is forced false, likewise. Most real Skyscrapers puzzles have their visibility counts determined by the outer clues well before the Latin square is fully nailed down, and CP-SAT exploits this directly.
Two corner cases tighten the model without changing its surface. A clue of on a given edge forces the in the cell adjacent to that edge: nothing can be hidden behind a peak of height , so exactly the one building is visible. A clue of forces the line to be the strictly-increasing permutation : every building must be taller than all its predecessors. Either fact, fed into the model as a direct equality, cuts search further. Neither is needed for correctness; both are present in the propagator by implication.
Sources. Skyscrapers puzzles circulated at the World Puzzle Championship in the s and were popularised by Japanese publishers and Web-based puzzle sites in the following decade. The running-max reification used in the model is standard CP practice; see Simonis, Sudoku as a constraint problem (CP-AI-OR ) for a closely related treatment of the Latin square layer, and Mladenović et al., Solving Skyscrapers puzzles with constraint programming, Computers & Operations Research (), for the full machinery. The CP-SAT solver’s AddMaxEquality and reified-boolean mechanisms are documented at developers.google.com/optimization.