DSAPrep
HardBacktracking

N Queens

The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other.

Given an integer n, return all distinct solutions to the n-queens puzzle. You may return the answer in any order.

Each solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.' both indicate a queen and an empty space, respectively.

Example 1

Input: n = 4
Output: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
Explanation: There exist two distinct solutions to the 4-queens puzzle as shown above.

Example 2

Input: n = 1
Output: [["Q"]]

Constraints

  • 1 <= n <= 9
View original on LeetCode β†—

Two queens attack each other if they share a row, a column, or a diagonal. Since no two queens can ever share a row, it is safe to place exactly one queen per row and only decide which column it goes in β€” that turns an n^2-cell placement problem into an n-row decision problem.

Brute Force: Check Every Full Board

Time O(n^(2n))Space O(n^2)

Place one queen per row by trying every column for every row (n^n full placements), and only after a complete placement is built, scan all C(n,2) pairs of queens to check whether any two attack each other.

class Solution:
def solveNQueens(self, n: int) -> list[list[str]]:
result = []
cols_per_row = [0] * n
def is_valid_board():
for r1 in range(n):
for r2 in range(r1 + 1, n):
c1, c2 = cols_per_row[r1], cols_per_row[r2]
if c1 == c2 or abs(c1 - c2) == abs(r1 - r2):
return False
return True
def build(row):
if row == n:
if is_valid_board():
result.append([
'.' * c + 'Q' + '.' * (n - c - 1) for c in cols_per_row
])
return
for col in range(n):
cols_per_row[row] = col
build(row + 1)
build(0)
return result

Places all n^n full boards before ever checking validity, and each check costs O(n^2) β€” wasted effort, since a queen placed in row 2 might already conflict with row 0, making every one of the n^(n-2) boards built underneath it doomed.

Backtracking with Column/Diagonal Sets

OptimalTime O(n!)Space O(n)

Place queens row by row, but check safety before descending into the next row: track which columns, and which of the two diagonal families, already have a queen. A cell (r, c) lies on the same β€œ/” diagonal as any other cell with the same r + c, and the same β€œ\” diagonal as any other cell with the same r - c β€” so three sets are enough to check safety in O(1). The moment a row has no safe column left, that whole branch is abandoned immediately instead of being built out fully.

class Solution:
def solveNQueens(self, n: int) -> list[list[str]]:
result = []
cols = set()
diag1 = set() # r - c
diag2 = set() # r + c
board = [['.'] * n for _ in range(n)]
def backtrack(row):
if row == n:
result.append([''.join(r) for r in board])
return
for col in range(n):
if col in cols or (row - col) in diag1 or (row + col) in diag2:
continue # unsafe: shares a column or diagonal with an existing queen
cols.add(col)
diag1.add(row - col)
diag2.add(row + col)
board[row][col] = 'Q'
backtrack(row + 1)
board[row][col] = '.' # backtrack: undo the placement
cols.remove(col)
diag1.remove(row - col)
diag2.remove(row + col)
backtrack(0)
return result

Play the search below β€” the trace is generated by replaying the exact code above, so the board can never disagree with it. Watch the three attack sets in the side panel knock unsafe columns out of a row in one lookup each, and what happens when a row has nowhere left to go:

Board β€” row 0 is being decided

c0c1c2c3
β–Έ0
?
?
?
?
1
2
3

Attack sets β€” a cell (r, c) is safe only when its column, r - c, and r + c chips are all unlit

cols
0123
r + c (/ diag)
0123456
r - c (\ diag)
-3-2-10123
1 / 12
queen (placed)row being decidedattacked β€” unsafetried & reverted

n = 4, empty board, three empty attack sets. One queen per row, so a row only decides which column. A cell (r, c) is unsafe the moment its column, its r - c back diagonal, or its r + c forward diagonal is already claimed β€” three set lookups judge any cell in constant time, before any descent happens.

Both walls the search hits are visible in the trace: row 2 under queens at (0, 0) and (1, 2), then row 3 under (0, 0), (1, 3), and (2, 1), each die with zero placements β€” the sets judge a whole row before anything beneath it is explored. Backing out of column 0 entirely, row 0’s column 1 sails straight down to solution 1, [".Q..","...Q","Q...","..Q."] β€” one of the two solutions the example expects. The second solution, ["..Q.","Q...","...Q",".Q.."], is found symmetrically when row 0 backtracks again and tries column 2.

Why it is correct: a queen is only ever placed in a column/diagonal combination not already occupied, so every complete board reaching row == n is conflict-free by construction; conversely every valid board can be built by placing its queens row by row in this order, so nothing is missed. Complexity: row 0 has n choices, row 1 has at most n - 1 remaining safe columns, and so on β€” bounding the search by O(n!) time in the worst case (far better than the brute force’s n^n full placements, since unsafe branches are cut off immediately rather than fully built and checked afterward). The three sets and the board are O(n) and O(n^2) respectively, but the recursion depth itself β€” the dominant auxiliary cost per active path β€” is O(n) space.