-
Notifications
You must be signed in to change notification settings - Fork 273
Peaceful Chessboard
TIP103 Unit 12 Session 1 (Click for link to problem statements)
On an n x n chessboard you must place n queens so that no two threaten each other (no shared row, column, or diagonal).
Return the number of distinct ways to place the queens.
def total_n_queens(n):
pass- 💡 Difficulty: Hard
- ⏰ Time to complete: 30-40 mins
- 🛠️ Topics: Backtracking, Recursion, Sets
Understand what the interviewer is asking for by using test cases and questions about the problem.
- Established a set (2-3) of test cases to verify their own solution later.
- Established a set (1-2) of edge cases to verify their solution handles complexities.
- Have fully understood the problem and have no clarifying questions.
- Have you verified any Time/Space Constraints for this problem?
- Q: What does it mean for two queens to threaten each other?
- A: Two queens threaten each other if they share the same row, the same column, or the same diagonal (in either direction).
- Q: Do we need to return the actual board arrangements?
- A: No. We only need to return the count of distinct valid arrangements, not the boards themselves.
- Q: Since exactly
nqueens go on ann x nboard, what does that tell us about the rows?- A: Every row must contain exactly one queen. This lets us place queens row by row and only decide which column each one goes in.
HAPPY CASE
Input: n = 4
Output: 2
Explanation: There are exactly two ways to place 4 non-threatening queens on a 4x4 board (each is a mirror image of the other).
Input: n = 1
Output: 1
Explanation: A single queen on a 1x1 board threatens no one, so there is exactly 1 arrangement.
EDGE CASE
Input: n = 2
Output: 0
Explanation: On a 2x2 board, any two queens share a row, column, or diagonal, so no peaceful arrangement exists. (The same is true for n = 3.)
Match what this problem looks like to known categories of problems, e.g. Linked List or Dynamic Programming, and strategies or patterns in those categories.
For Constraint Satisfaction / Counting Problems, we can consider the following approaches:
- Backtracking: Build the board one row at a time. Place a queen in a column that is still safe, recurse to the next row, and undo the choice on the way back. This is the classic N-Queens backtracking pattern.
- Sets for O(1) conflict checks: Track occupied columns and diagonals in sets so each candidate square can be validated in constant time instead of rescanning the board.
Plan the solution with appropriate visualizations and pseudocode.
General Idea:
Place one queen per row, working from row 0 down to row n - 1. For each row, try every column; a column is safe if no earlier queen occupies that column, its "/" diagonal, or its "" diagonal. Squares on the same "" diagonal share the value row - col, and squares on the same "/" diagonal share the value row + col, so three sets are enough to detect every conflict. Each time we successfully place a queen in the last row, we have found one complete arrangement and count it.
1) Create three empty sets: cols, neg_diagonals (row - col), and pos_diagonals (row + col).
2) Define a recursive helper backtrack(row):
a) Base case: if row == n, every queen is placed peacefully, so return 1.
b) Initialize count = 0.
c) For each col from 0 to n - 1:
i) If col is in cols, or (row - col) is in neg_diagonals, or (row + col) is in pos_diagonals, skip it.
ii) Otherwise, add col, (row - col), and (row + col) to the sets (place the queen).
iii) Add backtrack(row + 1) to count.
iv) Remove col, (row - col), and (row + col) from the sets (un-place the queen).
d) Return count.
3) Return backtrack(0).
- Forgetting to remove the queen from the sets after the recursive call, which corrupts the state for sibling branches.
- Checking only rows and columns and forgetting one (or both) diagonal directions.
- Mixing up the diagonal identifiers:
row - colis constant along "" diagonals androw + colis constant along "/" diagonals. - Rescanning the whole board to validate each placement, which works but adds an unnecessary O(n) factor per check.
Implement the code to solve the algorithm.
def total_n_queens(n):
def backtrack(row):
# Base case: all n queens placed peacefully
if row == n:
return 1
count = 0
for col in range(n):
# Skip any column or diagonal already under attack
if col in cols or (row - col) in neg_diagonals or (row + col) in pos_diagonals:
continue
# Choose: place a queen at (row, col)
cols.add(col)
neg_diagonals.add(row - col)
pos_diagonals.add(row + col)
# Explore: place queens in the remaining rows
count += backtrack(row + 1)
# Un-choose: remove the queen and try the next column
cols.remove(col)
neg_diagonals.remove(row - col)
pos_diagonals.remove(row + col)
return count
cols = set() # Columns that already hold a queen
neg_diagonals = set() # "\" diagonals, identified by row - col
pos_diagonals = set() # "/" diagonals, identified by row + col
return backtrack(0)Review the code by running specific example(s) and recording values (watchlist) of your code's variables along the way.
-
Input: n = 4
- Row 0, col 0: placing here eventually dead-ends — rows 1-3 run out of safe columns, so this branch contributes 0.
- Row 0, col 1: leads to the valid board with queens at columns (1, 3, 0, 2), contributing 1.
- Row 0, col 2: leads to the mirror board with queens at columns (2, 0, 3, 1), contributing 1.
- Row 0, col 3: dead-ends like col 0, contributing 0.
- Output: 2
-
Input: n = 1
- Row 0, col 0 is safe;
backtrack(1)hits the base case and returns 1. - Output: 1
- Row 0, col 0 is safe;
Evaluate the performance of your algorithm and state any strong/weak or future potential work.
Assume N is the side length of the board (and the number of queens).
-
Time Complexity:
O(N!)— the first row hasNcolumn choices, and each subsequent row has strictly fewer safe columns, so the search tree is bounded byN * (N - 1) * (N - 2) * .... Each placement check isO(1)thanks to the sets. -
Space Complexity:
O(N)for the recursion stack (one frame per row) and the three conflict sets, each of which holds at mostNentries.