51. N-Queens
Problem Description
The n-queens puzzle asks you to place n
queens on an n x n
chessboard such that no two queens can attack each other.
In chess, a queen can attack any piece that is:
- In the same row
- In the same column
- On the same diagonal (both main diagonal and anti-diagonal)
Given an integer n
, you need to find all possible valid arrangements of n
queens on the board.
Each solution should be represented as a list of strings where:
- Each string represents one row of the chessboard
'Q'
indicates a queen's position'.'
indicates an empty square
For example, if n = 4
, one valid solution would be:
[".Q..", "...Q", "Q...", "..Q."]
This represents a 4x4 board where queens are placed at positions (0,1), (1,3), (2,0), and (3,2), and no two queens can attack each other.
The function should return all distinct valid board configurations. The order of solutions doesn't matter.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: The problem involves placing queens on a chessboard, not traversing nodes and edges in a graph structure.
Need to solve for kth smallest/largest?
- No: We're not looking for the kth smallest or largest element. We need to find all valid board configurations.
Involves Linked Lists?
- No: The problem uses a 2D board representation, not linked list data structures.
Does the problem have small constraints?
- Yes: The n-queens problem typically has small constraints (n is usually ≤ 15 in most cases). The problem requires exploring all possible queen placements, which grows exponentially with n. For small values of n, this is manageable.
Brute force / Backtracking?
- Yes: We need to systematically explore all possible queen placements. Backtracking is perfect here because:
- We place queens one by one
- If a placement leads to a conflict, we backtrack and try a different position
- We need to find ALL valid solutions, not just one
- The problem involves making choices (where to place each queen) and undoing them if they don't work
Conclusion: The flowchart correctly leads us to use a Backtracking approach. This makes sense because we need to:
- Try placing a queen in each possible position
- Check if the placement is valid (no conflicts with other queens)
- If valid, recursively place the next queen
- If we reach a dead end, backtrack by removing the queen and trying the next position
- Collect all valid complete board configurations
Intuition
When we think about placing queens on a chessboard, we need to ensure no two queens can attack each other. Since we need to place n
queens on an n x n
board, and queens can attack any piece in their row, we know that each row must contain exactly one queen.
This observation immediately simplifies our problem - instead of considering all possible positions on the board, we can process the board row by row, deciding which column to place the queen in for each row.
The key insight is that when we place a queen at position (i, j)
, it "blocks" certain positions for future queens:
- Column
j
is blocked for all subsequent rows - The diagonal going through
(i, j)
is blocked - The anti-diagonal going through
(i, j)
is blocked
To track these constraints efficiently, we can use a clever indexing trick:
- For columns: simply use index
j
- For diagonals: cells on the same diagonal share the same value of
i + j
- For anti-diagonals: cells on the same anti-diagonal share the same value of
n - i + j
(ori - j + n - 1
)
This means we can use three arrays to mark which columns and diagonals are already occupied by queens.
The backtracking approach naturally emerges from this:
- Start with row 0
- Try placing a queen in each column of the current row
- Check if the position is safe (column and both diagonals are free)
- If safe, mark the position as occupied and move to the next row
- If we successfully place queens in all rows, we found a solution
- If we can't place a queen in any column of the current row, backtrack to the previous row and try the next column
The beauty of this approach is that by processing row by row, we automatically ensure no two queens are in the same row, and our tracking arrays ensure no conflicts in columns or diagonals.
Learn more about Backtracking patterns.
Solution Approach
The implementation uses a depth-first search (DFS) with backtracking to systematically explore all valid queen placements.
Data Structures Used:
g
: A 2D list representing the chessboard, initialized with all'.'
(empty squares)col
: An array of sizen
to track which columns have queens (col[j] = 1
if columnj
has a queen)dg
: An array of size2n
for diagonal tracking (using indexi + j
)udg
: An array of size2n
for anti-diagonal tracking (using indexn - i + j
)ans
: List to store all valid board configurations
Core Algorithm - DFS Function:
The dfs(i)
function places queens starting from row i
:
-
Base Case: If
i == n
, we've successfully placed alln
queens. Convert the current board stateg
into the required string format and add it toans
. -
Recursive Case: For the current row
i
, try placing a queen in each columnj
from 0 ton-1
:-
Check if position is safe: Verify that
col[j] + dg[i + j] + udg[n - i + j] == 0
- This clever check ensures the column, diagonal, and anti-diagonal are all free (all values are 0)
-
Place the queen: If safe, update the board and tracking arrays:
g[i][j] = "Q" col[j] = dg[i + j] = udg[n - i + j] = 1
-
Recurse: Call
dfs(i + 1)
to place queens in the next row -
Backtrack: After recursion returns, restore the original state:
col[j] = dg[i + j] = udg[n - i + j] = 0 g[i][j] = "."
-
Why the Diagonal Indexing Works:
- Main diagonal (
i + j
): Points on the same diagonal from top-left to bottom-right have the same sum of coordinates. Range is[0, 2n-2]
. - Anti-diagonal (
n - i + j
): Points on the same diagonal from top-right to bottom-left can be indexed this way. Addingn
ensures all indices are positive, range is[1, 2n-1]
.
Time Complexity: O(n!)
- In the worst case, we explore all possible placements. The first queen has n
choices, the second has at most n-2
, and so on.
Space Complexity: O(n)
for the recursion stack depth, plus O(n²)
for storing the board state.
The solution elegantly combines the constraint tracking with backtracking to efficiently find all valid n-queens configurations without redundant checks.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through solving the 4-queens problem step by step.
We start with a 4×4 board and need to place 4 queens. Our tracking arrays are:
col[4]
= [0,0,0,0] (tracks columns)dg[8]
= [0,0,0,0,0,0,0,0] (tracks diagonals using i+j)udg[8]
= [0,0,0,0,0,0,0,0] (tracks anti-diagonals using n-i+j)
Step 1: Row 0, Try column 0
- Check position (0,0): col[0]=0, dg[0+0]=0, udg[4-0+0]=0 ✓ All clear!
- Place queen at (0,0): Set col[0]=1, dg[0]=1, udg[4]=1
- Board state:
Q...
- Move to row 1
Step 2: Row 1, Try columns
- Column 0: col[0]=1 ✗ (blocked by queen at row 0)
- Column 1: col[1]=0, dg[1+1]=0, udg[4-1+1]=1 ✗ (anti-diagonal blocked)
- Column 2: col[2]=0, dg[1+2]=0, udg[4-1+2]=0 ✓ All clear!
- Place queen at (1,2): Set col[2]=1, dg[3]=1, udg[5]=1
- Board state:
Q...
/..Q.
- Move to row 2
Step 3: Row 2, Try columns
- Column 0: col[0]=1 ✗ (blocked)
- Column 1: col[1]=0, dg[2+1]=1 ✗ (diagonal blocked by queen at (1,2))
- Column 2: col[2]=1 ✗ (blocked)
- Column 3: col[3]=0, dg[2+3]=1 ✗ (diagonal blocked by queen at (1,2))
- No valid position! Backtrack to row 1
Step 4: Back to Row 1, continue from column 3
- Remove queen from (1,2): Reset col[2]=0, dg[3]=0, udg[5]=0
- Column 3: col[3]=0, dg[1+3]=0, udg[4-1+3]=0 ✓ All clear!
- Place queen at (1,3): Set col[3]=1, dg[4]=1, udg[6]=1
- Board state:
Q...
/...Q
Step 5: Row 2, Try columns
- Column 0: col[0]=1 ✗
- Column 1: col[1]=0, dg[2+1]=0, udg[4-2+1]=0 ✓ All clear!
- Place queen at (2,1): Set col[1]=1, dg[3]=1, udg[3]=1
- Board state:
Q...
/...Q
/.Q..
Step 6: Row 3, Try columns
- Column 0: col[0]=1 ✗
- Column 1: col[1]=1 ✗
- Column 2: col[2]=0, dg[3+2]=0, udg[4-3+2]=1 ✗ (anti-diagonal blocked)
- Column 3: col[3]=1 ✗
- No valid position! Backtrack to row 2
This process continues, exploring different combinations through backtracking. Eventually, we find a valid solution when:
- Queen at (0,1): col[1]=1, dg[1]=1, udg[5]=1
- Queen at (1,3): col[3]=1, dg[4]=1, udg[6]=1
- Queen at (2,0): col[0]=1, dg[2]=1, udg[2]=1
- Queen at (3,2): col[2]=1, dg[5]=1, udg[3]=1
Final solution:
.Q.. ...Q Q... ..Q.
The algorithm continues this backtracking process until all possibilities are explored, finding both solutions for the 4-queens problem.
Solution Implementation
1class Solution:
2 def solveNQueens(self, n: int) -> List[List[str]]:
3 def backtrack(row: int) -> None:
4 """
5 Recursively place queens row by row using backtracking.
6
7 Args:
8 row: Current row index to place a queen
9 """
10 # Base case: all queens are successfully placed
11 if row == n:
12 # Convert the board to the required string format and add to results
13 solution = ["".join(row_chars) for row_chars in board]
14 results.append(solution)
15 return
16
17 # Try placing a queen in each column of the current row
18 for col in range(n):
19 # Check if placing queen at (row, col) is safe
20 if (columns_used[col] == 0 and
21 diagonals_used[row + col] == 0 and
22 anti_diagonals_used[n - row + col] == 0):
23
24 # Place the queen
25 board[row][col] = "Q"
26
27 # Mark the column and diagonals as occupied
28 columns_used[col] = 1
29 diagonals_used[row + col] = 1
30 anti_diagonals_used[n - row + col] = 1
31
32 # Recursively place queens in the next row
33 backtrack(row + 1)
34
35 # Backtrack: remove the queen and unmark the positions
36 columns_used[col] = 0
37 diagonals_used[row + col] = 0
38 anti_diagonals_used[n - row + col] = 0
39 board[row][col] = "."
40
41 # Initialize the result list to store all valid solutions
42 results = []
43
44 # Initialize the chess board with all empty cells
45 board = [["."] * n for _ in range(n)]
46
47 # Track which columns are occupied by queens
48 columns_used = [0] * n
49
50 # Track which diagonals are occupied (row + col identifies each diagonal)
51 # Maximum possible value is (n-1) + (n-1) = 2n-2, so we need 2n-1 elements
52 diagonals_used = [0] * (2 * n)
53
54 # Track which anti-diagonals are occupied (n - row + col identifies each anti-diagonal)
55 # Range is from n - (n-1) + 0 = 1 to n - 0 + (n-1) = 2n-1, so we need 2n elements
56 anti_diagonals_used = [0] * (2 * n)
57
58 # Start the backtracking from row 0
59 backtrack(0)
60
61 return results
62
1class Solution {
2 // List to store all valid N-Queens solutions
3 private List<List<String>> solutions = new ArrayList<>();
4
5 // Array to track if a column is occupied by a queen
6 private int[] columnOccupied;
7
8 // Array to track if a diagonal (top-left to bottom-right) is occupied
9 // Diagonals with same (row + col) value belong to the same diagonal
10 private int[] mainDiagonal;
11
12 // Array to track if an anti-diagonal (top-right to bottom-left) is occupied
13 // Anti-diagonals with same (n - row + col) value belong to the same anti-diagonal
14 private int[] antiDiagonal;
15
16 // 2D array representing the chess board
17 private String[][] board;
18
19 // Size of the chess board (n x n)
20 private int boardSize;
21
22 /**
23 * Solves the N-Queens problem and returns all possible solutions.
24 * @param n The size of the chess board (n x n)
25 * @return List of all valid board configurations
26 */
27 public List<List<String>> solveNQueens(int n) {
28 this.boardSize = n;
29
30 // Initialize tracking arrays
31 columnOccupied = new int[n];
32 mainDiagonal = new int[n * 2]; // Maximum possible diagonals: 2n - 1
33 antiDiagonal = new int[n * 2]; // Maximum possible anti-diagonals: 2n - 1
34
35 // Initialize the board with empty cells
36 board = new String[n][n];
37 for (int row = 0; row < n; ++row) {
38 Arrays.fill(board[row], ".");
39 }
40
41 // Start the depth-first search from row 0
42 placeQueens(0);
43
44 return solutions;
45 }
46
47 /**
48 * Recursively places queens on the board using backtracking.
49 * @param currentRow The current row being processed
50 */
51 private void placeQueens(int currentRow) {
52 // Base case: all queens have been successfully placed
53 if (currentRow == boardSize) {
54 // Convert the board to the required string format and add to solutions
55 List<String> currentSolution = new ArrayList<>();
56 for (int row = 0; row < boardSize; ++row) {
57 currentSolution.add(String.join("", board[row]));
58 }
59 solutions.add(currentSolution);
60 return;
61 }
62
63 // Try placing a queen in each column of the current row
64 for (int col = 0; col < boardSize; ++col) {
65 // Check if the current position is safe (no conflicts with existing queens)
66 if (columnOccupied[col] + mainDiagonal[currentRow + col] +
67 antiDiagonal[boardSize - currentRow + col] == 0) {
68
69 // Place the queen
70 board[currentRow][col] = "Q";
71
72 // Mark the column and diagonals as occupied
73 columnOccupied[col] = 1;
74 mainDiagonal[currentRow + col] = 1;
75 antiDiagonal[boardSize - currentRow + col] = 1;
76
77 // Recursively place queens in the next row
78 placeQueens(currentRow + 1);
79
80 // Backtrack: remove the queen and unmark the positions
81 columnOccupied[col] = 0;
82 mainDiagonal[currentRow + col] = 0;
83 antiDiagonal[boardSize - currentRow + col] = 0;
84 board[currentRow][col] = ".";
85 }
86 }
87 }
88}
89
1class Solution {
2public:
3 vector<vector<string>> solveNQueens(int n) {
4 // Track which columns are occupied by queens
5 vector<int> colOccupied(n);
6
7 // Track which diagonals are occupied (top-left to bottom-right)
8 // For diagonal: row + col is constant, range: [0, 2n-2]
9 vector<int> diagonalOccupied(n << 1);
10
11 // Track which anti-diagonals are occupied (top-right to bottom-left)
12 // For anti-diagonal: row - col + n is constant, range: [1, 2n-1]
13 vector<int> antiDiagonalOccupied(n << 1);
14
15 // Store all valid board configurations
16 vector<vector<string>> result;
17
18 // Current board state during backtracking
19 vector<string> currentBoard(n, string(n, '.'));
20
21 // Recursive function to place queens row by row
22 function<void(int)> backtrack = [&](int row) -> void {
23 // Base case: all queens successfully placed
24 if (row == n) {
25 result.push_back(currentBoard);
26 return;
27 }
28
29 // Try placing a queen in each column of the current row
30 for (int col = 0; col < n; ++col) {
31 // Check if current position is safe (no conflicts)
32 if (colOccupied[col] + diagonalOccupied[row + col] +
33 antiDiagonalOccupied[n - row + col] == 0) {
34
35 // Place the queen
36 currentBoard[row][col] = 'Q';
37
38 // Mark the column and diagonals as occupied
39 colOccupied[col] = 1;
40 diagonalOccupied[row + col] = 1;
41 antiDiagonalOccupied[n - row + col] = 1;
42
43 // Recursively place queens in the next row
44 backtrack(row + 1);
45
46 // Backtrack: remove the queen and unmark positions
47 colOccupied[col] = 0;
48 diagonalOccupied[row + col] = 0;
49 antiDiagonalOccupied[n - row + col] = 0;
50 currentBoard[row][col] = '.';
51 }
52 }
53 };
54
55 // Start placing queens from the first row
56 backtrack(0);
57
58 return result;
59 }
60};
61
1/**
2 * Solves the N-Queens problem and returns all valid board configurations
3 * @param n - The size of the chess board (n x n)
4 * @returns Array of valid board configurations where 'Q' represents a queen and '.' represents empty
5 */
6function solveNQueens(n: number): string[][] {
7 // Track if a column is occupied by a queen
8 const columnsOccupied: number[] = Array(n).fill(0);
9
10 // Track if a diagonal (top-left to bottom-right) is occupied
11 // Diagonals are indexed by row + column
12 const diagonals: number[] = Array(n << 1).fill(0);
13
14 // Track if an anti-diagonal (top-right to bottom-left) is occupied
15 // Anti-diagonals are indexed by n - row + column
16 const antiDiagonals: number[] = Array(n << 1).fill(0);
17
18 // Store all valid solutions
19 const solutions: string[][] = [];
20
21 // Current board state during backtracking
22 const currentBoard: string[][] = Array.from({ length: n }, () => Array(n).fill('.'));
23
24 /**
25 * Depth-first search to place queens row by row
26 * @param currentRow - The current row being processed
27 */
28 const backtrack = (currentRow: number): void => {
29 // Base case: all queens have been successfully placed
30 if (currentRow === n) {
31 // Convert the 2D board array to array of strings and add to solutions
32 solutions.push(currentBoard.map(row => row.join('')));
33 return;
34 }
35
36 // Try placing a queen in each column of the current row
37 for (let column = 0; column < n; ++column) {
38 // Check if current position is safe (no conflicts with existing queens)
39 if (columnsOccupied[column] + diagonals[currentRow + column] + antiDiagonals[n - currentRow + column] === 0) {
40 // Place the queen
41 currentBoard[currentRow][column] = 'Q';
42
43 // Mark the column and diagonals as occupied
44 columnsOccupied[column] = 1;
45 diagonals[currentRow + column] = 1;
46 antiDiagonals[n - currentRow + column] = 1;
47
48 // Recursively place queens in the next row
49 backtrack(currentRow + 1);
50
51 // Backtrack: remove the queen and unmark positions
52 columnsOccupied[column] = 0;
53 diagonals[currentRow + column] = 0;
54 antiDiagonals[n - currentRow + column] = 0;
55 currentBoard[currentRow][column] = '.';
56 }
57 }
58 };
59
60 // Start the backtracking process from the first row
61 backtrack(0);
62
63 return solutions;
64}
65
Time and Space Complexity
Time Complexity: O(n² × n!)
The algorithm uses backtracking to explore all possible queen placements. For each valid solution found, there are several operations:
- The recursive function
dfs
explores at mostn!
valid board configurations (upper bound on the number of ways to place n queens) - At each recursive level
i
, we iterate throughn
columns to try placing a queen - When a complete solution is found (
i == n
), we construct the answer by iterating through then × n
board, which takesO(n²)
time - The total time complexity is
O(n × n!)
for exploring all configurations, but since we needO(n²)
time to construct each solution, the overall complexity becomesO(n² × n!)
Space Complexity: O(n)
The space usage consists of:
- The recursion stack depth is at most
n
(one recursive call per row):O(n)
- The
col
array of sizen
:O(n)
- The
dg
(diagonal) array of size2n
:O(n)
- The
udg
(anti-diagonal) array of size2n
:O(n)
- The board
g
isn × n
:O(n²)
- The answer list
ans
stores the solutions but is not counted in auxiliary space complexity
While the board g
takes O(n²)
space, the reference answer considers O(n)
as the space complexity, likely focusing on the auxiliary space used by the recursion stack and tracking arrays, excluding the space needed to store the input/output representation.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Diagonal Indexing Formula
Pitfall: One of the most common mistakes is using incorrect formulas for diagonal indexing, such as:
- Using
row - col
for diagonals (which can produce negative indices) - Using
row + col + n
for anti-diagonals (which wastes array space) - Mixing up the formulas for diagonals and anti-diagonals
Why it happens: The diagonal indexing is counterintuitive. Without understanding why row + col
and n - row + col
work, developers often guess at formulas.
Solution:
- For main diagonals (↘): Use
row + col
. Points on the same diagonal have the same sum. - For anti-diagonals (↙): Use
n - row + col
orrow - col + n - 1
. The offset ensures all indices are non-negative. - Draw a small grid and verify your indexing manually before coding.
2. Forgetting to Backtrack Properly
Pitfall: Failing to reset the state after exploring a branch:
# Incorrect - forgetting to reset the board board[row][col] = "Q" columns_used[col] = 1 diagonals_used[row + col] = 1 anti_diagonals_used[n - row + col] = 1 backtrack(row + 1) # Missing the backtrack step!
Why it happens: It's easy to focus on the forward recursion and forget that backtracking requires undoing changes.
Solution: Always ensure every state change has a corresponding reset:
# Place queen board[row][col] = "Q" columns_used[col] = 1 diagonals_used[row + col] = 1 anti_diagonals_used[n - row + col] = 1 # Explore backtrack(row + 1) # Backtrack - undo all changes in reverse order anti_diagonals_used[n - row + col] = 0 diagonals_used[row + col] = 0 columns_used[col] = 0 board[row][col] = "."
3. Creating Board Representation Incorrectly
Pitfall: Creating the board with shared references:
# Incorrect - all rows reference the same list! board = [["."] * n] * n # When you modify board[0][0], all board[i][0] change
Why it happens: Python's list multiplication creates references, not copies, for mutable objects.
Solution: Use list comprehension to create independent rows:
# Correct - each row is a separate list
board = [["."] * n for _ in range(n)]
4. Converting Board to Output Format Incorrectly
Pitfall: Forgetting to create a deep copy of the board state when saving solutions:
# Incorrect - appending references to the same board if row == n: results.append(board) # This saves a reference, not a copy!
Why it happens: The board array is reused throughout the recursion. Saving references means all solutions will show the final board state.
Solution: Convert the board to strings immediately when saving:
# Correct - create new string representations if row == n: solution = ["".join(row_chars) for row_chars in board] results.append(solution)
5. Off-by-One Errors in Array Sizing
Pitfall: Incorrectly sizing the tracking arrays:
# Incorrect - too small for diagonal tracking diagonals_used = [0] * n # Should be 2*n anti_diagonals_used = [0] * n # Should be 2*n
Why it happens: Not calculating the maximum possible index values. For an n×n board:
row + col
ranges from 0 to 2n-2n - row + col
ranges from 1 to 2n-1
Solution: Always allocate 2*n
elements for diagonal tracking arrays to safely cover all possible indices:
diagonals_used = [0] * (2 * n) anti_diagonals_used = [0] * (2 * n)
Which data structure is used to implement priority queue?
Recommended Readings
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!