The n-queens puzzle is the problem of placing n queens on an n x n chessboard so that no two queens can attack each other.
A queen in a chessboard can attack horizontally, vertically, and diagonally.
Given an integer n, return all distinct solutions to the n-queens puzzle.
Each solution contains a unique board layout where the queen pieces are placed. 'Q' indicates a queen and '.' indicates an empty space.
You may return the answer in any order.
Example 1:
Input: n = 4
Output: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]Explanation: There are two different solutions to the 4-queens puzzle.
Example 2:
Input: n = 1
Output: [["Q"]]Constraints:
1 <= n <= 8
You should aim for a solution with O(n!) time and O(n^2) space, where n is the size of the given square board.
A queen can move in 8 directions, and no two queens can be in the same row or column. This means we can place one queen per row or column. We iterate column-wise and try to place a queen in each column while ensuring no other queen exists in the same row, left diagonal, or left bottom diagonal. Can you think of a recursive algorithm to find all possible combinations?
We can use backtracking to traverse through the columns with index c while maintaining a board that represents the current state in the recursive path. We reach the base condition when c == n and we add a copy of the board to the result. How would you implement this?
We initialize an empty board and recursively go through each column. For each column, we check each cell to see if we can place a queen there. We use a function to check if the cell is suitable by iterating along the left directions and verifying if the same row, left diagonal, or left bottom diagonal are free. If it is possible, we place the queen on the board, move along the recursive path, and then backtrack by removing the queen to continue to the next cell in the column.
The goal is to place one queen in each row such that no two queens attack each other.
Key observations:
This is a classic backtracking + constraint checking problem.
n x n board filled with ".".0.r (row):c (column).isSafe:"Q")n rows are filled:class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
res = []
board = [["."] * n for i in range(n)]
def backtrack(r):
if r == n:
copy = ["".join(row) for row in board]
res.append(copy)
return
for c in range(n):
if self.isSafe(r, c, board):
board[r][c] = "Q"
backtrack(r + 1)
board[r][c] = "."
backtrack(0)
return res
def isSafe(self, r: int, c: int, board):
row = r - 1
while row >= 0:
if board[row][c] == "Q":
return False
row -= 1
row, col = r - 1, c - 1
while row >= 0 and col >= 0:
if board[row][col] == "Q":
return False
row -= 1
col -= 1
row, col = r - 1, c + 1
while row >= 0 and col < len(board):
if board[row][col] == "Q":
return False
row -= 1
col += 1
return TrueInstead of checking the board every time to see if a queen is safe, we remember the attacked positions using hash sets.
For any queen at position (row, col):
col(row + col)(row - col)By storing these in sets, we can check whether a position is safe in O(1) time.
We still place one queen per row, move row by row, and backtrack when a placement leads to a conflict.
col → tracks used c (columns)posDiag → tracks (row + col)negDiag → tracks (row - col)n x n board with ".".0.r (row):c (column)c, (r + c), or (r - c) is already in the sets → skipc, (r + c), (r - c) to the sets"Q" on the boardclass Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
col = set()
posDiag = set()
negDiag = set()
res = []
board = [["."] * n for i in range(n)]
def backtrack(r):
if r == n:
copy = ["".join(row) for row in board]
res.append(copy)
return
for c in range(n):
if c in col or (r + c) in posDiag or (r - c) in negDiag:
continue
col.add(c)
posDiag.add(r + c)
negDiag.add(r - c)
board[r][c] = "Q"
backtrack(r + 1)
col.remove(c)
posDiag.remove(r + c)
negDiag.remove(r - c)
board[r][c] = "."
backtrack(0)
return resThis approach is the array-based version of the hash-set solution.
Instead of using sets, we use boolean arrays to mark whether a column or diagonal is already occupied by a queen.
This works because:
nFor a queen at position (row, col):
colrow + colrow - col + n (shifted to avoid negative index)If any of these positions are already marked True, placing a queen there would cause a conflict.
We place queens row by row, and backtrack when no safe column is available.
col[n] → tracks occupied columnsposDiag[2n] → tracks row + colnegDiag[2n] → tracks row - col + nn x n board filled with ".".0.r (row):c (column)col[c], posDiag[r+c], or negDiag[r-c+n] is true, skipcol[c], posDiag[r+c], negDiag[r-c+n] as true"Q" on the board at (r, c)r + 1r == n:class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
col = [False] * n
posDiag = [False] * (n * 2)
negDiag = [False] * (n * 2)
res = []
board = [["."] * n for i in range(n)]
def backtrack(r):
if r == n:
copy = ["".join(row) for row in board]
res.append(copy)
return
for c in range(n):
if col[c] or posDiag[r + c] or negDiag[r - c + n]:
continue
col[c] = True
posDiag[r + c] = True
negDiag[r - c + n] = True
board[r][c] = "Q"
backtrack(r + 1)
col[c] = False
posDiag[r + c] = False
negDiag[r - c + n] = False
board[r][c] = "."
backtrack(0)
return resThis is the most optimized backtracking approach for the N-Queens problem.
Instead of using arrays or hash sets to track occupied columns and diagonals, we use bit masks (integers).
Each bit represents whether a column or diagonal is already occupied.
Why this works well:
For a queen placed at position (row, col):
col/) → bit (row + col)\) → bit (row - col + n)If any of these bits are already set, placing a queen there causes a conflict.
We still place queens row by row, but conflict checks are done using bitwise AND.
col → tracks used columnsposDiag → tracks row + colnegDiag → tracks row - col + nn x n board filled with ".".0.c (column) in the current r (row):col & (1 << c)posDiag & (1 << (r + c))negDiag & (1 << (r - c + n))^=) to mark column and diagonals"Q" at (r, c)r + 1r == n:class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
col = 0
posDiag = 0
negDiag = 0
res = []
board = [["."] * n for i in range(n)]
def backtrack(r):
nonlocal col, posDiag, negDiag
if r == n:
copy = ["".join(row) for row in board]
res.append(copy)
return
for c in range(n):
if ((col & (1 << c)) or (posDiag & (1 << (r + c)))
or (negDiag & (1 << (r - c + n)))):
continue
col ^= (1 << c)
posDiag ^= (1 << (r + c))
negDiag ^= (1 << (r - c + n))
board[r][c] = "Q"
backtrack(r + 1)
col ^= (1 << c)
posDiag ^= (1 << (r + c))
negDiag ^= (1 << (r - c + n))
board[r][c] = "."
backtrack(0)
return resA frequent mistake is checking only whether a column is occupied while neglecting diagonal conflicts. Queens attack along both diagonals, so you must verify the upper-left diagonal (where row - col is constant) and the upper-right diagonal (where row + col is constant). Missing either diagonal check will produce invalid board configurations.
When using arrays to track diagonal occupancy, the negative diagonal row - col can produce negative indices. You must offset this value by adding n (i.e., use row - col + n) to ensure valid array indices. Forgetting this offset causes array index out of bounds errors or incorrect diagonal tracking.
After recursively exploring a placement and returning, you must undo the state changes (remove the queen from sets/arrays and reset the board position to "."). Failing to properly backtrack leaves stale state that incorrectly blocks future valid placements, causing the algorithm to miss valid solutions.
Since queens are placed row by row from top to bottom, only rows above the current row can contain previously placed queens. A common error is scanning the entire column or both diagonal directions (up and down). This is wasteful at best and can cause incorrect behavior if the board is not properly initialized. Only check upward directions: the column above, upper-left diagonal, and upper-right diagonal.
When a valid configuration is found, you must create a copy of the board before adding it to the results. Simply appending a reference to the same board object means all stored solutions will reflect subsequent modifications during backtracking. Always convert each row to a new string and create a fresh list for each solution.