Hello! Welcome to the final lesson in our backtracking module.
In our previous lessons, we used backtracking to generate all possible permutations and subsets. The main challenge was to explore every possibility without generating duplicates. Today, we'll tackle a different kind of problem where the focus shifts from generating everything to finding configurations that satisfy a specific set of rules.
This brings us to the concept of Constraint Satisfaction Problems (CSPs). These are problems where you need to find a state or configuration that adheres to a given set of constraints. Our backtracking template is perfectly suited for this, but with a twist: we'll use the constraints to "prune" the decision tree, abandoning invalid paths as early as possible.
The learning outcome for this lesson is to solve constraint satisfaction problems, like N-Queens, using backtracking. We'll use the famous N-Queens puzzle as our primary example to master this technique.
1. What is a Constraint Satisfaction Problem?
At its core, a CSP consists of three components. To understand them, let's start with a high-level overview.
Constraint-Satisfaction Problems in Python
The article 'Constraint-Satisfaction Problems in Python' provides a clear, formal introduction to the components of a CSP. Understanding this framework will help you recognize and model similar problems in the future.
Read the first few paragraphs of the article, stopping at 'Figure 1'. Focus on understanding the three core concepts: variables, domains, and constraints.
As the article explains, a CSP is defined by:
- Variables: The decision points or objects you need to assign values to.
- Domains: The set of possible values each variable can take.
- Constraints: The rules that a valid solution must obey.
Our goal is to find an assignment of values to variables from their domains that satisfies all constraints. Backtracking is a natural fit for solving CSPs because it systematically explores assignments and backtracks as soon as a constraint is violated.

2. The N-Queens Puzzle as a CSP
The N-Queens puzzle is a classic CSP. The goal is to place N chess queens on an N×N chessboard so that no two queens threaten each other.
To understand the rules and how they translate into constraints, watch the beginning of this video from NeetCode.
N-Queens - Backtracking - Leetcode 51 - Python
This video clearly explains the N-Queens problem and the constraints that define a valid solution.
Watch from 00:29 to 05:47. Pay close attention to how a queen attacks (row, column, and diagonals) and the key insight that simplifies the problem: we can decide to place exactly one queen in each row.
Let's formally define the N-Queens puzzle as a CSP:
- Variables: The video gives us a crucial hint. Since each queen must be in a different row, we can simplify the problem. Let's say our variables are
q_0, q_1, ..., q_{N-1}, whereq_rrepresents the queen in rowr. - Domain: The value we need to assign to each variable
q_ris its column index. So, for an N×N board, the domain for each variable is{0, 1, ..., N-1}. - Constraints: Given any two queens,
q_r1at(r1, c1)andq_r2at(r2, c2):- Same Row:
r1 ≠r2. This is automatically satisfied by our choice of variables! - Same Column:
c1 ≠c2. - Same Diagonal: They cannot be on the same diagonal.
- Same Row:
The main challenge is how to efficiently check the column and diagonal constraints.
3. The Backtracking Algorithm and Efficient Constraint Checking
Our backtracking strategy will be to place one queen per row, from row 0 to row N-1. For each row, we will iterate through all possible columns and check if placing a queen there is "safe".
- Choose: Pick a safe column
cfor the current rowr. - Explore: Recursively call the function for the next row,
r + 1. - Un-choose: If the recursive call returns (meaning it has explored all paths from that choice), remove the queen from
(r, c)to explore other columns for rowr.
But how do we check if (r, c) is safe in constant time? Checking the column is easy—we can use a set to store occupied columns. The diagonals are trickier, but there's a clever mathematical property we can use.
51. N-Queens - In-Depth Explanation
The 'Intuition' section of the Algo.monster article on N-Queens explains this elegant trick for tracking diagonals.
Read the 'Intuition' section. Focus on the explanation of how the sum i + j and difference i - j remain constant along diagonals.
To summarize the key insight:
- For all squares
(r, c)on a positive diagonal (top-left to bottom-right), the valuer + cis constant. - For all squares
(r, c)on a negative diagonal (top-right to bottom-left), the valuer - cis constant.
This means we can use three sets to keep track of our constraints in O(1) time:
- A set for occupied columns.
- A set for occupied positive diagonals (identified by
r + c). - A set for occupied negative diagonals (identified by
r - c).
When we want to place a queen at (r, c), we just check if c, r+c, and r-c are already in our sets.
4. Code Implementation
Now, let's see how this all comes together in code. The following video provides a complete walkthrough of a Python implementation.
N-Queens - Backtracking - Leetcode 51 - Python
This part of the NeetCode video walks through the full Python implementation, showing how the sets are used to check constraints within the backtracking function.
Watch from 05:47 to 17:23. Follow along as the backtrack function is built. Pay special attention to the base case, the check for constraints, and the 'choose' and 'un-choose' steps where the sets are updated.
Here's a Python implementation similar to the one in the video.
def solveNQueens(n):
# Sets to store occupied columns and diagonals
col_set = set()
pos_diag_set = set() # (r + c)
neg_diag_set = set() # (r - c)
result = []
# Initialize an empty board
board = [["."] * n for _ in range(n)]
def backtrack(r):
# Base Case: If we've placed queens in all rows
if r == n:
# Format the board and add it to the result
copy = ["".join(row) for row in board]
result.append(copy)
return
# Try placing a queen in each column of the current row 'r'
for c in range(n):
# --- Constraint Check ---
if c in col_set or (r + c) in pos_diag_set or (r - c) in neg_diag_set:
continue # This position is not safe, try next column
# --- Choose ---
col_set.add(c)
pos_diag_set.add(r + c)
neg_diag_set.add(r - c)
board[r][c] = "Q"
# --- Explore ---
backtrack(r + 1)
# --- Un-choose (Backtrack) ---
col_set.remove(c)
pos_diag_set.remove(r + c)
neg_diag_set.remove(r - c)
board[r][c] = "."
backtrack(0) # Start the process from row 0
return result
# Example for n=4
solutions = solveNQueens(4)
for sol in solutions:
for row in sol:
print(row)
print("-" * 10)
# Expected Output (order might differ):
# .Q..
# ...Q
# Q...
# ..Q.
# ----------
# ..Q.
# Q...
# ...Q
# .Q..
# ----------
Test your understanding!
In the backtrack function, why is it essential to perform the "un-choose" step (removing from sets and resetting the board) after the recursive call backtrack(r + 1) returns?
Show answer
The "un-choose" step is the core of backtracking. After exploring all possible solutions that can be built from a certain choice (e.g., placing a queen at (r, c)), we must undo that choice. This frees up the column and diagonals associated with (r, c), allowing the for loop to explore other choices for the current row r (e.g., placing a queen at (r, c+1)). Without this step, the constraints from the first choice would incorrectly affect all subsequent choices in the same row.
5. Walkthrough and Complexity
To solidify your understanding, let's trace the algorithm for a smaller n.
51. N-Queens - In-Depth Explanation
The Algo.monster article provides an excellent step-by-step walkthrough for the 4-Queens problem. This will help you visualize how the algorithm explores the decision tree and uses the tracking sets.
Read the 'Example Walkthrough' section. Follow how the algorithm places queens, checks constraints, and backtracks when it hits a dead end.
Complexity Analysis
For competitive programming, understanding the performance of your algorithm is critical.
- Time Complexity:
O(N!). This is a loose upper bound, but it captures the factorial nature of the problem. For the first row, we haveNchoices, for the second at mostN-1, and so on. The actual number of operations is much smaller due to early pruning by the diagonal constraints, butO(N!)is the standard accepted complexity. (Note: A factor ofN^2is sometimes included for building the string representation of each solution found, making itO(N^2 * N!)). - Space Complexity:
O(N^2)to store the board and the results. The recursion depth isN, and the sets also store up toO(N)elements, but theO(N^2)space for the board dominates.
Conclusion
Congratulations on completing the backtracking module! You have progressed from simple generation problems to a complex constraint satisfaction problem. This is a significant step in your competitive programming journey.
Key Takeaways:
- The N-Queens puzzle is a classic Constraint Satisfaction Problem (CSP) that can be modeled with variables (queen in each row), domains (columns), and constraints (no attacks).
- Backtracking provides a natural template to solve CSPs by exploring choices and pruning the search space as soon as a constraint is violated.
- For N-Queens, the key to an efficient solution is using sets and the properties of diagonals (
r+candr-c) to perform O(1) constraint checks. - The "Choose, Explore, Un-choose" pattern remains central, but the "Choose" step now includes a critical validity check.
Up Next:
We will now move to a completely new topic: Heaps and Priority Queues. We'll leave the world of recursive search and explore a tree-based data structure that is incredibly efficient for problems involving finding the minimum or maximum element, such as "Top K" problems and implementing Dijkstra's algorithm.