Snakes and Ladders
Snakes and Ladders is LeetCode 909 (Medium). On an n × n board numbered in boustrophedon order, return the least number of dice moves needed to reach square n², or -1 if it cannot be reached.
The board is at most 20 × 20, so only 400 squares exist and the search is never the bottleneck. The numbering is.
- n == board.length == board[i].length
- 2 <= n <= 20
- board[i][j] is either -1 or in the range [1, n²].
- The squares labeled 1 and n² do not have snakes or ladders.
Intuition
Every square is a node and each of the six dice outcomes is an edge, so the problem is a shortest path on an unweighted graph — plain BFS, no weights to balance. The search is the easy part; two things around it are where answers go wrong:
- The numbering. Squares run boustrophedon: from the bottom-left, alternating direction each row going up. Convert a square to the wrong row or column and the BFS runs perfectly on the wrong board.
- The jump rule. A snake or ladder is taken once per move. Landing on a ladder whose top holds a snake's mouth does not then follow the snake.
A board game whose moves all cost the same is an unweighted shortest-path problem, so BFS applies directly. The distinctive work here is a numbering scheme, not an algorithm: whenever a problem labels its cells unusually, isolate the conversion in a helper and keep the search generic.
Approach
Before reading on: take a 6x6 board and work out the row and column of square 15 by hand, then write the general formula and check it against squares 1, 6, 7 and 36. Getting that conversion right is most of this problem. Aim for O(n²).
Start BFS from square 1
Queue square 1 with 0 moves and mark it seen. Pop a square s and try each roll, s + 1 up to min(s + 6, n²). Every roll costs one move, so BFS reaches each square first by the fewest rolls; Dijkstra would be pointless on uniform weights.
Convert square numbers to coordinates in a helper
The boustrophedon board snakes back and forth by row from the bottom up. For square s, take q, r = divmod(s - 1, n):
- the row is
n - 1 - q, counting up from the bottom; - the column is
rwhenqis even, butn - 1 - rwhenqis odd.
Keep it in its own helper — folding it into the search is where this goes wrong.
Apply at most one snake or ladder per move
After landing, if the cell is not -1, jump to the square it names and stop there. Do not loop. Following a chain of jumps reports fewer rolls than the rules actually permit, and the boards in the examples are built to expose exactly that.
Mark the square you end up on
Record the destination after any jump, not the square the dice landed on. Two different rolls that redirect to the same place are the same state, so marking it once stops the queue filling with duplicates of a square already reached sooner.
Count moves on the queue, not by level
Carry the move count alongside each square in the queue and return moves + 1 the instant the final square is generated. Because every edge costs one, the first arrival is already the minimum and there is no need to finish the level.
Return -1 when the queue drains
If BFS exhausts every reachable square without ever generating the last one, then no sequence of rolls arrives there and the answer is -1. This is a real outcome, not a defensive guard: a board can wall the final square off behind snakes.
Snakes and Ladders solution in Python | C++ | Java
Common pitfalls
Getting the boustrophedon direction backwards
col = remainder
col = remainder if quotient % 2 == 0 else n - 1 - remainder
Alternate rows are numbered right to left. Using the plain remainder reads the wrong cell on every odd row, so snakes and ladders appear in the wrong places and the answer is silently wrong.
Chaining jumps
while board[row][col] != -1:
nxt = board[row][col]
row, col = coordinates(nxt)if board[row][col] != -1:
nxt = board[row][col]The rules permit at most one snake or ladder per move. Following a chain of them reports fewer rolls than are legally possible.
Marking the pre-jump square as visited
visited.add(square + step)
visited.add(nxt) # after the jump is applied
The square you actually occupy is the jump's destination. Marking the intermediate square lets the same destination be enqueued repeatedly and can block a genuinely shorter route.
Edge cases
The answer is the pure dice minimum, since a roll of six is always available and nothing redirects it.
Only one jump is taken per move, so the ladder is followed and the snake is not. Chaining them would under-report the answer.
The queue drains without ever generating the last square, and the function returns -1.
Square 4 is within a single roll of square 1, so the answer is 1 — the smallest board the constraints allow.