Two approaches to LeetCode N-Queens β brute force O(n^n) and backtracking with column/diagonal sets O(n!). Java and Python solutions with visual walkthrough.
LeetCode Β· #51
N-Queens Solution
Place n non-attacking queens on an n Γ n chessboard. Return all distinct board configurations where no two queens share a row, column, or diagonal.
Place n queens on an n Γ n chessboard so that no two queens attack each other (no shared row, column, or diagonal). Return all distinct solutions as board grids where 'Q' marks a queen and '.' marks an empty cell.
Example (n = 4)
Input: n = 4Output: [[".Q..","...Q","Q...","..Q."],
["..Q.","Q...","...Q",".Q.."]]
// Two valid configurations exist for n=4
Constraints
β’ 1 β€ n β€ 9 β at most 352 solutions for n=9. Backtracking is feasible.β’ A queen attacks all cells in the same row, column, main diagonal (r-c=const), and anti-diagonal (r+c=const).
02
Section Two Β· Approach 1
Brute Force β Try All Placements
Generate all ways to place n queens anywhere on the nΓn board (nα΅ possibilities). After placing all n queens, validate the board by checking every pair of queens for conflicts. Return configurations with no conflicts.
Why it works
Exhaustive enumeration with post-hoc validation guarantees finding every valid configuration. The correctness is trivial β all placements are tried and bad ones discarded.
Why we can do better
Problem: O(n^(nΒ²)) placement candidates β for n=9 that is 9^81, astronomically large. We can prune early by placing one queen per row (since two queens in the same row is always invalid). This reduces placement candidates to n! immediately. Further, checking constraints with O(1) hash sets instead of scanning the board gives us O(n!) overall calls with O(1) validation per step.
Java β One-per-row backtracking (already smarter than full brute force)
import java.util.*;
classSolution {
publicList<List<String>> solveNQueens(int n) {
List<List<String>> res = newArrayList<>();
char[][] board = newchar[n][n];
for (char[] row : board) Arrays.fill(row, '.');
backtrack(board, 0, res);
return res;
}
privatevoidbacktrack(char[][] board, int row,
List<List<String>> res) {
if (row == board.length) {
res.add(toBoard(board)); return;
}
for (int col = 0; col < board.length; col++) {
if (isValid(board, row, col)) { // O(n) scan β will optimize
board[row][col] = 'Q';
backtrack(board, row + 1, res);
board[row][col] = '.';
}
}
}
privatebooleanisValid(char[][] b, int r, int c) {
for (int i = 0; i < r; i++) { // check column and diagonals aboveif (b[i][c] == 'Q') returnfalse;
if (c-(r-i) >= 0 && b[i][c-(r-i)] == 'Q') returnfalse;
if (c+(r-i) < b.length && b[i][c+(r-i)] == 'Q') returnfalse;
}
returntrue;
}
privateList<String> toBoard(char[][] b) {
List<String> board = newArrayList<>();
for (char[] row : b) board.add(newString(row));
return board;
}
}
Python β One-per-row with O(n) validation
classSolution:
defsolveNQueens(self, n: int) -> list[list[str]]:
board = [['.']*n for _ in range(n)]
res = []
defis_valid(r: int, c: int) -> bool:
for i in range(r):
diff = r - i
if board[i][c] == 'Q': returnFalseif c-diff >= 0and board[i][c-diff] == 'Q': returnFalseif c+diff < n and board[i][c+diff] == 'Q': returnFalsereturnTruedefbt(row: int) -> None:
if row == n:
res.append([''.join(r) for r in board])
returnfor col in range(n):
ifis_valid(row, col):
board[row][col] = 'Q'bt(row + 1)
board[row][col] = '.'bt(0)
return res
Metric
Value
Time
O(n! Β· n) β n! placements, O(n) validation each
Space
O(nΒ²) board + O(n) call stack
03
Section Three Β· Approach 2
Backtracking with O(1) Constraint Sets
Instead of scanning the board on each placement, maintain three hash sets: cols (occupied columns), diag1 (rowβcol constants), and diag2 (row+col constants). Any placement is valid iff its column and both diagonal keys are absent from these sets.
π‘ Mental model: Imagine a security manager assigning guards to rows in a building, one per floor. She keeps three logs on her clipboard: which columns are occupied, which "/" diagonals are blocked, and which "\" diagonals are blocked. Before placing a guard on floor n at position c, she checks all three logs in O(1). If clear, she adds c, (nβc), and (n+c) to the logs, places the guard, and moves to the next floor. On backtracking, she un-logs all three entries.
Algorithm β O(1) constraint check at each step
Place one queen per row β guarantees no two queens share a row.
cols stores used column indices β rejects same-column placements.
diag1 stores used row β col values β rejects same "\" diagonal placements.
diag2 stores used row + col values β rejects same "/" diagonal placements.
On place: add all three keys. On backtrack: remove all three keys. Set board[row][col] = 'Q' and '.' to match.
Base case:row == n β convert board to list of strings and add to result.
π― When to recognize this pattern:
Problems placing items on a grid with constraint propagation β "no two in the same row/column/diagonal." The key insight is using mathematical invariants (rβc, r+c) to represent diagonals as integers, enabling O(1) lookup.
This exact pattern also solves LC 52 (N-Queens II β count solutions only).
Why rβc identifies a diagonal:
All cells on the same "\" diagonal have the same rβc value. All cells on the same "/" diagonal have the same r+c value.
Storing these in sets lets you check both diagonal constraints in O(1) instead of scanning the board row by row.
04
Section Four Β· Trace
Visual Walkthrough
Trace for n = 4, finding the first valid configuration. Columns tried at each row and pruning decisions shown.
classSolution:
defsolveNQueens(self, n: int) -> list[list[str]]:
cols, diag1, diag2 = set(), set(), set()
board = [['.']*n for _ in range(n)]
res = []
defbacktrack(row: int) -> None:
if row == n:
res.append([''.join(r) for r in board])
returnfor col in range(n):
if col in cols or (row-col) in diag1 or (row+col) in diag2:
continue# O(1) constraint check
board[row][col] = 'Q'
cols.add(col); diag1.add(row-col); diag2.add(row+col)
backtrack(row + 1)
board[row][col] = '.'# backtrack
cols.discard(col)
diag1.discard(row-col)
diag2.discard(row+col)
backtrack(0)
return res
06
Section Six Β· Analysis
Complexity Analysis
Approach
Time
Space
Trade-off
Brute force all positions
O(n^(nΒ²))
O(nΒ²)
Completely infeasible for n > 3
One-per-row + O(n) validation
O(n! Β· n)
O(nΒ²)
Feasible but each check scans the board
One-per-row + O(1) sets β optimal
O(n!)
O(n)
Constant-time constraint check β strictly better than O(n!) Β· n
Why does the set approach reduce the time constant?:
Both approaches make the same number of recursive calls (O(n!) in the pruned tree).
The difference is the per-call cost: scanning the board is O(n) while O(1) set lookups eliminate the inner loop.
For n=9, the reduction is significant β n=9 means removing 9Γ work per recursive call.
07
Section Seven Β· Edge Cases
Edge Cases & Pitfalls
Case
Input
Why It Matters
n = 1
n=1
Only one cell β the single queen has no attackers. Result: [["Q"]].
n = 2, n = 3
n=2 or n=3
No valid placement exists. Result: []. Algorithm explores and prunes all branches.
n = 4
n=4
Two solutions. Both are found by continuing backtracking after the first.
Diagonal key collision
negative rβc
rβc can be negative (e.g., row=0, col=3 β β3). HashSet handles negative integers correctly.
Not removing from all sets
any n
Forgetting to remove from any of the 3 sets on backtrack leaves stale constraints β future rows incorrectly see blocked positions.
n = 9 (max)
n=9
352 valid solutions. O(9!) β 362,880 max calls. Runs well under 1ms.
β Common Mistake: Using only a cols set and forgetting to track diagonals. A column check alone lets queens attack each other diagonally. You need both diagonal sets: diag1 for "\" diagonals (rowβcol constant) and diag2 for "/" diagonals (row+col constant). Missing either set produces configurations where queens attack diagonally.