LeetCode #909 Medium

Snakes and Ladders

Snakes and Ladders: on an n×n boustrophedon board, return the least number of dice moves needed to reach the last square, or -1 if it is unreachable.

Constraints
  • 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.
arraybreadth-first-searchmatrix
Open on LeetCode ↗
02

Intuition

Each square connects to the six ahead of it, so the board is a graph and every move costs one roll — BFS finds the fewest rolls. The only real complication is the boustrophedon numbering, which is best isolated in a helper that converts a square number into board coordinates.

How to spot this pattern

Board games with uniform-cost moves are unweighted shortest-path problems, so BFS applies directly. The distinctive work here is the coordinate mapping — whenever a problem uses an unusual numbering, isolate the conversion in a helper and keep the algorithm generic.

03

Approach

Try it first

Before reading on: work out the row and column of square 15 on a 6×6 board by hand, then write the general formula. Keeping that conversion separate from the search is most of the battle. Aim for O(n²).

1

Isolate the coordinate conversion

Squares are numbered from the bottom-left, running alternately right and left as you go up. For square s (1-indexed), let q = (s - 1) // n and r = (s - 1) % n. The row from the bottom is q, so the array row is n - 1 - q. The column is r on even q and n - 1 - r on odd q, because those rows run backwards. Writing this as a small helper keeps the BFS itself completely free of index arithmetic — mixing the two is where most bugs on this problem come from.

2

BFS over squares, not cells

Treat the square number as the node. From square s, the reachable neighbours are s+1 through s+6, capped at . For each, look up the board value: if it is not −1, a snake or ladder redirects the move to that destination instead. Enqueue the resulting square with one more roll and mark it visited. The first time square is dequeued — or reached — the roll count is minimal, because BFS expands in order of distance.

3

One jump per move, never chained

Landing on a ladder moves you to its top, but if that top holds another snake or ladder you do not take it — the rules allow at most one jump per roll. So apply the redirection once and stop. Marking the destination as visited rather than the intermediate square is what enforces this naturally. The board has n² squares each with 6 outgoing edges, giving O(n²) time and space.

04

Solution & live demo

1class Solution:
2 def snakesAndLadders(self, board):
3 n = len(board)
4 target = n * n
5 
6 def coordinates(square):
7 quotient, remainder = divmod(square - 1, n)
8 row = n - 1 - quotient
9 col = remainder if quotient % 2 == 0 else n - 1 - remainder
10 return row, col
11 
12 visited = {1}
13 queue = deque([(1, 0)])
14 while queue:
15 square, moves = queue.popleft()
16 for step in range(1, 7):
17 nxt = square + step
18 if nxt > target:
19 break
20 row, col = coordinates(nxt)
21 if board[row][col] != -1:
22 nxt = board[row][col]
23 if nxt == target:
24 return moves + 1
25 if nxt not in visited:
26 visited.add(nxt)
27 queue.append((nxt, moves + 1))
28 return -1
05

Common pitfalls

Getting the boustrophedon direction backwards

✗ Wrong
col = remainder
✓ Right
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

✗ Wrong
while board[row][col] != -1:
    nxt = board[row][col]
    row, col = coordinates(nxt)
✓ Right
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

✗ Wrong
visited.add(square + step)
✓ Right
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.

06

Edge cases

Board with no snakes or ladders

The answer is ⌈(n²−1)/6⌉, the pure dice minimum.

Unreachable final square

The queue drains without reaching n² and the answer is -1.

Ladder directly to the end

Found on the first move, giving 1.

Snake at the destination of a ladder

Only one jump is taken per move, so the snake is not chained.

2×2 board

Square 4 is within one roll of square 1, so the answer is 1.

07

Complexity

Time
O(n²)
Space
O(n²)
Each of the n² squares is enqueued at most once and has at most six outgoing moves.