

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Still calibrating
click for more info
Not enough gems
Cost: 6 gems
1: N Queens
incomplete
2: Validate Boards
incomplete
3: First Solution
incomplete
4: All Solutions
incomplete
5: Safe Positions
incomplete
6: Backtracking
incomplete
7: Pruning
incomplete
8: Complexity
incomplete
This lesson's interactive features are locked, please to keep using them
Backtracking is way better than brute force, but it's still doing extra work on every move. Each time we consider a new queen position, is_safe loops over the whole board to ask:
While that's cheap on a small board, each check gets slower as the partial board grows. We can make the same decision faster by tracking which columns and diagonals are already taken.
We're already pruning the tree of possible boards by cutting off branches the moment we know they can't work. Now we're just going to make each of those pruning checks cheaper.
Columns are simple. Once we place a queen in column 3, column 3 is off-limits for every future row.
We can track the used columns with a set:
used_cols = set()
And the column check collapses to a single lookup:
col in used_cols
Diagonals are a bit more complicated, but they follow patterns that we can use.
For one diagonal direction, going up and to the right, every square on the same diagonal has the same row + col value:
row + col = 2
The (row, col) values and their sums:
(0, 2) → 0 + 2 = 2(1, 1) → 1 + 1 = 2(2, 0) → 2 + 0 = 2For the other direction, going down and to the right, every square on the same diagonal has the same row - col value:
row - col = -1
The (row, col) values and their differences:
(0, 1) → 0 - 1 = -1(1, 2) → 1 - 2 = -1(2, 3) → 2 - 3 = -1So we can catch a diagonal conflict by checking row + col against a set of used "up diagonals," and row - col against a set of used "down diagonals":
row + col in used_up_diagonals
row - col in used_down_diagonals
Put it all together and testing a (row, col) position is three quick lookups – one for the column, two for the diagonals:
col in used_cols
row + col in used_up_diagonals
row - col in used_down_diagonals
Look ma, no loops!
The backtracking pattern stays the same:
We'll just need to update the sets alongside the board. When we place a queen:
board.append(col)
used_cols.add(col)
used_up_diagonals.add(row + col)
used_down_diagonals.add(row - col)
And after the recursive call, we undo all of it:
board.pop()
used_cols.remove(col)
used_up_diagonals.remove(row + col)
used_down_diagonals.remove(row - col)
This way, the current board and the "used" sets stay in sync.
Complete the solve_n_queens function, using backtracking with more efficient pruning. The function accepts a board size n (int) and returns all valid board arrangements (list[list[int]]).
This implementation gives another performance boost, so we can try larger values of n. The test cases go up to n = 12, which has well over 10,000 solutions!