Chessboard and Queens
Problem Overview
| Attribute | Value |
|---|---|
| Difficulty | Medium |
| Category | Backtracking |
| Time Limit | 1 second |
| Key Technique | Backtracking with Pruning |
| CSES Link | Chessboard and Queens |
Learning Goals
After solving this problem, you will be able to:
- Understand and implement backtracking with constraint checking
- Use diagonal indexing to detect queen attacks in O(1) time
- Handle reserved/blocked squares in constraint satisfaction problems
- Recognize when to prune search branches early for efficiency
Problem Statement
Problem: Place 8 queens on an 8x8 chessboard so that no two queens attack each other. Some squares are reserved and cannot have a queen. Count all valid arrangements.
Input:
- 8 lines, each with 8 characters
.represents an empty square (queen can be placed)*represents a reserved square (queen cannot be placed)
Output:
- Number of valid ways to place 8 non-attacking queens
Constraints:
- Board is always 8x8
- Queens attack horizontally, vertically, and diagonally
Example
Input:
........
........
..*.....
........
........
.....**.
...*....
........
Output:
65
Explanation: With some squares reserved (*), there are 65 valid configurations where 8 queens can be placed without attacking each other.
Intuition: How to Think About This Problem
Pattern Recognition
Key Question: How do we systematically explore all queen placements while efficiently detecting conflicts?
This is a classic constraint satisfaction problem solved with backtracking. We place queens row by row, and at each step, we check if the placement is valid. If we reach a dead end (no valid column for current row), we backtrack.
Breaking Down the Problem
- What are we looking for? All configurations of 8 queens with no attacks
- What constraints exist? No two queens on same row, column, or diagonal; no queen on reserved squares
- Why backtracking? We can prune entire branches when we detect a conflict early
The Key Insight: Diagonal Indexing
For an 8x8 board, there are:
- 15 main diagonals (top-left to bottom-right): indexed by
row - col + 7(range 0-14) - 15 anti-diagonals (top-right to bottom-left): indexed by
row + col(range 0-14)
Main Diagonal (row - col + 7): Anti-Diagonal (row + col):
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+
| 7| 8| 9|10|11|12|13|14| 0 | 0| 1| 2| 3| 4| 5| 6| 7| 0
| 6| 7| 8| 9|10|11|12|13| 1 | 1| 2| 3| 4| 5| 6| 7| 8| 1
| 5| 6| 7| 8| 9|10|11|12| 2 | 2| 3| 4| 5| 6| 7| 8| 9| 2
| 4| 5| 6| 7| 8| 9|10|11| 3 | 3| 4| 5| 6| 7| 8| 9|10| 3
| 3| 4| 5| 6| 7| 8| 9|10| 4 | 4| 5| 6| 7| 8| 9|10|11| 4
| 2| 3| 4| 5| 6| 7| 8| 9| 5 | 5| 6| 7| 8| 9|10|11|12| 5
| 1| 2| 3| 4| 5| 6| 7| 8| 6 | 6| 7| 8| 9|10|11|12|13| 6
| 0| 1| 2| 3| 4| 5| 6| 7| 7 | 7| 8| 9|10|11|12|13|14| 7
+--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+
Two queens at (r1, c1) and (r2, c2) are on the same diagonal if:
- Main:
r1 - c1 == r2 - c2 - Anti:
r1 + c1 == r2 + c2
Solution: Backtracking with Pruning
Idea
Place exactly one queen in each row. For each row, try all 8 columns. Skip if:
- The square is reserved (
*) - The column already has a queen
- Either diagonal already has a queen
Track occupied columns and diagonals with boolean arrays for O(1) lookup.
Algorithm
- Read the board and mark reserved squares
- Initialize tracking arrays:
col_used[8],diag1_used[15],diag2_used[15] - Recursively place queens row by row:
- Base case: row == 8, found valid configuration, count++
- For each column in current row:
- Skip if reserved, column used, or diagonal used
- Mark column and diagonals as used
- Recurse to next row
- Unmark (backtrack)
- Return total count
Dry Run Example
Let’s trace through a small portion with an empty 8x8 board:
Row 0: Try each column
col=0: Place queen at (0,0)
Mark: col_used[0]=true, diag1[7]=true, diag2[0]=true
Row 1: Try each column
col=0: Skip (col_used[0] is true)
col=1: Skip (diag1[7] - same main diagonal as (0,0))
col=2: Place queen at (1,2)
Mark: col_used[2]=true, diag1[6]=true, diag2[3]=true
Row 2: Try each column
col=0: Skip (col_used[0] is true)
col=1: Skip (diag2[3] - same anti-diagonal as (1,2))
col=2: Skip (col_used[2] is true)
col=3: Skip (diag1[6] - same main diagonal as (1,2))
col=4: Place queen at (2,4)
...continue recursion...
If all 8 rows placed successfully -> count++
Backtrack and try next column configurations
Visual: One Valid Configuration
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
0 | Q | | | | | | | | Queen at col 0
+---+---+---+---+---+---+---+---+
1 | | | | | Q | | | | Queen at col 4
+---+---+---+---+---+---+---+---+
2 | | | | | | | | Q | Queen at col 7
+---+---+---+---+---+---+---+---+
3 | | | | | | Q | | | Queen at col 5
+---+---+---+---+---+---+---+---+
4 | | | Q | | | | | | Queen at col 2
+---+---+---+---+---+---+---+---+
5 | | | | | | | Q | | Queen at col 6
+---+---+---+---+---+---+---+---+
6 | | Q | | | | | | | Queen at col 1
+---+---+---+---+---+---+---+---+
7 | | | | Q | | | | | Queen at col 3
+---+---+---+---+---+---+---+---+
No two queens share a row, column, or diagonal!
Code (Python)
def solve():
"""
CSES Chessboard and Queens - Backtracking Solution
Time: O(8!) worst case, much less with pruning
Space: O(8) for recursion stack and tracking arrays
"""
# Read the 8x8 board
board = []
for _ in range(8):
board.append(input().strip())
# Tracking arrays for O(1) conflict detection
col_used = [False] * 8 # col_used[c] = True if column c has a queen
diag1 = [False] * 15 # Main diagonal: row - col + 7
diag2 = [False] * 15 # Anti diagonal: row + col
count = 0
def backtrack(row):
nonlocal count
# Base case: all 8 queens placed successfully
if row == 8:
count += 1
return
# Try each column in current row
for col in range(8):
# Skip if square is reserved
if board[row][col] == '*':
continue
# Calculate diagonal indices
d1 = row - col + 7 # Main diagonal index
d2 = row + col # Anti diagonal index
# Skip if column or diagonal is already used
if col_used[col] or diag1[d1] or diag2[d2]:
continue
# Place queen: mark column and diagonals
col_used[col] = True
diag1[d1] = True
diag2[d2] = True
# Recurse to next row
backtrack(row + 1)
# Backtrack: unmark column and diagonals
col_used[col] = False
diag1[d1] = False
diag2[d2] = False
backtrack(0)
print(count)
# Run solution
solve()
Complexity
| Metric | Value | Explanation |
|---|---|---|
| Time | O(8!) | At most 8! = 40,320 configurations; pruning reduces this significantly |
| Space | O(1) | Fixed-size arrays (8 + 15 + 15); O(8) recursion depth |
Common Mistakes
Mistake 1: Wrong Diagonal Index Calculation
# WRONG - can produce negative indices
d1 = row - col # Range: -7 to 7
# CORRECT - shift to non-negative range
d1 = row - col + 7 # Range: 0 to 14
Problem: Negative array indices cause bugs or wrap-around errors. Fix: Add offset (N-1 = 7) to ensure all indices are non-negative.
Mistake 2: Forgetting to Check Reserved Squares
# WRONG - ignores reserved squares
for col in range(8):
if col_used[col] or diag1[d1] or diag2[d2]:
continue
# ... place queen
# CORRECT - check reserved squares first
for col in range(8):
if board[row][col] == '*': # Check reserved first!
continue
if col_used[col] or diag1[d1] or diag2[d2]:
continue
# ... place queen
Problem: Queens placed on reserved squares, giving wrong count. Fix: Check reserved status before any other validation.
Mistake 3: Off-by-One in Diagonal Array Size
# WRONG - array too small
diag1 = [False] * 8 # Only 8 elements
# CORRECT - need 2*N - 1 = 15 elements
diag1 = [False] * 15
Problem: For an NxN board, there are 2N-1 diagonals in each direction. Fix: Use array size of 15 (= 2*8 - 1) for both diagonal arrays.
Mistake 4: Not Backtracking Properly
# WRONG - forgetting to unmark
col_used[col] = True
backtrack(row + 1)
# Missing: col_used[col] = False
# CORRECT - always unmark after recursion
col_used[col] = True
backtrack(row + 1)
col_used[col] = False # Backtrack!
Problem: State “leaks” between branches, causing incorrect counts. Fix: Always restore state after recursive call returns.
Edge Cases
| Case | Input | Expected Output | Why |
|---|---|---|---|
| No reserved squares | All . |
92 | Classic N-Queens answer |
| All squares reserved | All * |
0 | No valid placement possible |
| First row blocked | Row 0 all * |
0 | Cannot place queen in row 0 |
| Single path | Strategic * placement |
1 | Only one configuration works |
| Diagonal blocked | Main diagonal all * |
Still valid | Other placements exist |
When to Use This Pattern
Use Backtracking When:
- You need to find ALL solutions (not just one)
- Problem has constraints that can prune the search space
- Solution is built incrementally (e.g., row by row)
- You can detect invalid partial solutions early
Optimization Tips:
- Use arrays instead of sets for O(1) lookup (as shown)
- Prune as early as possible (check constraints before recursing)
- Consider symmetry to reduce search space (not needed for CSES)
Pattern Recognition Checklist:
- Need to place items with mutual constraints? -> Backtracking
- Chess-related attacks? -> Diagonal indexing with row +/- col
- Counting configurations? -> DFS with backtracking
Related Problems
Easier (Do These First)
| Problem | Why It Helps |
|---|---|
| Permutations | Basic backtracking pattern |
| Creating Strings | Generating all permutations |
Similar Difficulty
| Problem | Key Difference |
|---|---|
| N-Queens (LeetCode 51) | Return actual configurations, not just count |
| N-Queens II (LeetCode 52) | Same as this problem but without reserved squares |
Harder (Do These After)
| Problem | New Concept |
|---|---|
| Sudoku Solver (LeetCode 37) | More complex constraints, 2D placement |
| Grid Paths (CSES) | Backtracking with path constraints |
Key Takeaways
- The Core Idea: Place queens row-by-row, using tracking arrays to detect conflicts in O(1)
- Time Optimization: Pruning invalid branches early avoids exploring dead ends
- Space Trade-off: O(1) extra space for tracking arrays enables O(1) conflict detection
- Pattern: Classic constraint satisfaction with backtracking and diagonal indexing
Practice Checklist
Before moving on, make sure you can:
- Solve this problem without looking at the solution
- Explain why
row - col + 7androw + colidentify diagonals - Handle reserved squares correctly
- Implement backtracking with proper state restoration
- Write the solution in under 15 minutes
Additional Resources
- CP-Algorithms: Backtracking
- Wikipedia: Eight Queens Puzzle
- CSES Chessboard and Queens - Backtracking puzzle