[Hard] 51. 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.
Examples
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
Thinking Process
- Row-by-Row Placement: Eliminates row conflicts automatically
- Diagonal:
row - col + n(shifted to avoid negatives) - Anti-diagonal:
row + col
- Diagonal:
- Build solution incrementally; undo (backtrack) when constraints fail.
- Prune branches early to avoid exploring invalid partial states.
- Sort input to skip duplicate combinations efficiently.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Choose / explore / unchoose (this problem) | O(2^n) | O(n) | Subsets, combinations |
| Constraint pruning | Reduced search | O(n) | Early exit on invalid partial |
| Sort + skip duplicates | O(2^n) | O(n) | Combination sum II style |
| Path recording | O(n!) worst | O(n) | Permutations |
Solution
Solution: Backtracking with Optimized Constraint Checking
class Solution:
def solveNQueens(self, n):
self.size = n
self.board = [["."] * n for _ in range(n)]
self.col = [False] * n
self.diag = [False] * (2 * n)
self.anti = [False] * (2 * n)
self.rtn = []
self.dfs(0)
return self.rtn
def dfs(self, row):
if row == self.size:
self.rtn.append(["".join(r) for r in self.board])
return
for c in range(self.size):
d = row - c + self.size
a = row + c
if self.col[c] or self.diag[d] or self.anti[a]:
continue
self.col[c] = self.diag[d] = self.anti[a] = True
self.board[row][c] = "Q"
self.dfs(row + 1)
self.board[row][c] = "."
self.col[c] = self.diag[d] = self.anti[a] = False
Solution Explanation
Approach: Choose / explore / unchoose (this problem)
Key idea: 1. Row-by-Row Placement: Eliminates row conflicts automatically
How the code works:
- Row-by-Row Placement: Eliminates row conflicts automatically
- Diagonal:
row - col + n(shifted to avoid negatives) - Anti-diagonal:
row + col - Build solution incrementally; undo (backtrack) when constraints fail.
- Prune branches early to avoid exploring invalid partial states.
- Sort input to skip duplicate combinations efficiently.
- Diagonal:
Walkthrough — input n = 4, expected output [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]:
There exist two distinct solutions to the 4-queens puzzle as shown above.
Algorithm Explanation:
- Initialize (Lines 4-9):
size = n: Store board sizeboard:n × ngrid initialized with'.'col: Boolean array of sizento track used columnsdiag: Boolean array of size2 * nto track diagonals (top-left to bottom-right)anti: Boolean array of size2 * nto track anti-diagonals (top-right to bottom-left)
- DFS Function (Lines 16-30):
- Base Case (Lines 17-20): If
row == size, all queens placed successfully- Add current board configuration to result
- Return
- Try Each Column (Lines 21-29):
- Calculate indices:
d = row - c + size: Diagonal index (addsizeto avoid negative indices)a = row + c: Anti-diagonal index
- Check Constraints (Line 23): If column, diagonal, or anti-diagonal is occupied, skip
- Place Queen (Lines 24-25): Mark constraints and place
'Q' - Recurse (Line 26): Try next row
- Backtrack (Lines 27-28): Remove queen and unmark constraints
- Calculate indices:
- Base Case (Lines 17-20): If
Why This Works:
- Row-by-Row: Placing one queen per row eliminates row conflicts automatically
- Column Tracking:
col[c]ensures no two queens in same column - Diagonal Tracking:
- Diagonal
\:row - colis constant (shifted by+sizeto avoid negatives) - Anti-diagonal
/:row + colis constant
- Diagonal
- Optimization: Boolean arrays provide O(1) constraint checking vs O(n) board scanning
- Backtracking: Undoing choices allows exploring all valid configurations
Diagonal Index Calculation:
For an n × n board:
- Diagonal (top-left to bottom-right):
row - colranges from-(n-1)to(n-1)- Add
nto shift range to[1, 2n-1] - Use
row - col + nas index
- Add
- Anti-diagonal (top-right to bottom-left):
row + colranges from0to2(n-1)- Use
row + coldirectly as index
- Use
Example for n = 4:
Diagonal indices (row - col + 4):
0 1 2 3
0 4 5 6 7
1 3 4 5 6
2 2 3 4 5
3 1 2 3 4
Anti-diagonal indices (row + col):
0 1 2 3
0 0 1 2 3
1 1 2 3 4
2 2 3 4 5
3 3 4 5 6
Example Walkthrough:
For n = 4:
Initial: row=0, board=[[".",".",".","."], ...], all constraints false
Row 0:
Try col 0:
d = 0 - 0 + 4 = 4, a = 0 + 0 = 0
Check: col[0]=false, diag[4]=false, anti[0]=false ✓
Place Q at (0,0), mark constraints
Recurse to row 1
Row 1:
Try col 0: col[0]=true ✗
Try col 1:
d = 1 - 1 + 4 = 4, a = 1 + 1 = 2
Check: col[1]=false, diag[4]=true ✗ (conflict with (0,0))
Try col 2:
d = 1 - 2 + 4 = 3, a = 1 + 2 = 3
Check: col[2]=false, diag[3]=false, anti[3]=false ✓
Place Q at (1,2), mark constraints
Recurse to row 2
Row 2:
Try col 0: col[0]=true ✗
Try col 1:
d = 2 - 1 + 4 = 5, a = 2 + 1 = 3
Check: col[1]=false, diag[5]=false, anti[3]=true ✗
Try col 2: col[2]=true ✗
Try col 3:
d = 2 - 3 + 4 = 3, a = 2 + 3 = 5
Check: col[3]=false, diag[3]=true ✗
Backtrack: Remove Q from (1,2)
Try col 3:
d = 1 - 3 + 4 = 2, a = 1 + 3 = 4
Check: col[3]=false, diag[2]=false, anti[4]=false ✓
Place Q at (1,3), mark constraints
Recurse to row 2
... (continue until solution found)
Final: When row=4, add board to result
Complexity Analysis:
- Time Complexity: O(n!)
- For each row, we try at most
ncolumns - With pruning, actual complexity is better but still exponential
- In worst case: O(n!) (factorial)
- For each row, we try at most
- Space Complexity: O(n²)
board: O(n²) for storing board statecol,diag,anti: O(n) each- Recursion stack: O(n) depth
- Result: O(n! × n²) for storing all solutions
Common Mistakes
- n = 1: Return
[["Q"]] - n = 2, 3: No solutions (impossible to place n queens)
- n = 4: 2 solutions
-
n = 8: 92 solutions (classic 8-queens problem)
- Wrong diagonal indexing: Not shifting
row - colcorrectly - Missing backtrack: Forgetting to unmark constraints after recursion
- Array bounds: Diagonal array size should be
2 * n(notn) - Board initialization: Not initializing board with
'.'characters - Constraint checking order: Should check all three constraints before placing
Related Problems
- LC 52: N-Queens II - Count number of solutions (same problem, just count)
- LC 37: Sudoku Solver - Similar constraint satisfaction
- LC 51: N-Queens - This problem
Key Takeaways
- Row-by-Row Placement: Eliminates row conflicts automatically
- Optimized Constraint Checking: Boolean arrays provide O(1) checking vs O(n) scanning
- Diagonal Indexing:
- Diagonal:
row - col + n(shifted to avoid negatives) - Anti-diagonal:
row + col
- Diagonal:
- Backtracking Pattern: Place → Recurse → Undo
References
- LC 51: N-Queens on LeetCode
- LeetCode Discuss — LC 51: N-Queens
- LeetCode Editorial (may require premium)