N-Queens
Question
TheĀ n-queensĀ puzzle is the problem of placingĀ n
Ā queens on anĀ n x n
Ā chessboard such that no two queens attack each other.
Given an integerĀ n
, returnĀ all distinct solutions to theĀ n-queens puzzle. You may return the answer inĀ any order.
Each solution contains a distinct board configuration of the n-queens' placement, whereĀ 'Q'
Ā andĀ '.'
Ā both indicate a queen and an empty space, respectively.
Solution
Intuition
To solve this question, we should go row by row trying to place the queen at each row
.
If we can place n
queens at n
rows, that means there should be at least 1
queen at a row.
So we can do something like this:
Given this board, we can put a Queen Q
at any position in the board. Let's go row by row.
At the first row, we choose the put the queen at the first position.
This will cross out a few rows available for us base on chess rules:
The marker x
is what crossed out and can't place a queen there. Now at row = 1
, we only have 2 positions.
We try one of them:
This will give us no option at row = 1
. Therefore, we go back to the previous row and try the other position:
This will give us a position to go at row = 2
, We can place a Q
there.
Now at this row, we don't have any available position left to go. Therefore, we have to undo our moves and then start at the next Q
from row = 0
:
We continue our algorithm to check the next rows.
Quick way to mark unavailable position
Consider this following position:
Imagine after marking our queen Q
at (0, 1)
. We have to keep track of:
- The unavailable
column
: column1
- The unavailable
backwardDiagonal
(\
) :(1,2)
,(2,3)
- The unavailable
forwardDiagonal
(/
):(1, 0)
[!note]
We don't have to consider the unavailablerow
because we always moving to the next row after placing a queen. There is no reason to try out the current row
The column is simple, it's just the col
of the placement, in this case will be 1
.
For the backwardDiagonal
(\
ā the pink diagonal group), notice that all the positions in that group always follow the same formular:
$$col - row = 1$$
For example:
- Position
(0, 1)
:1 - 0 = 1
- Position
(1, 2)
:2 - 1 = 1
- Position
(2, 3)
:3 - 2 = 1
For the forwardDiagonal
(/
ā the purple diagonal group), notice that all the positions in that group always follow this formular:
$$row + col = 1$$
For example:
- Position
(0, 1)
:0 + 1 = 1
- Position
(1, 0)
:1 + 0 = 1
As a result, to keep track of these diagonal, we can just store col - row
and row + col
of the current marker Q
Implementation
class App:
EMPTY = "."
QUEEN = "Q"
def __init__(self):
self.result = []
self.occupiedColumn = set()
self.occupiedForwardDiagonal = set() # (row + col)
self.occupiedBackwardDiagonal = set() # (col - row)
def solveNQueens(self, n):
board = [[App.EMPTY for i in range(n)] for j in range(n)]
self.placeQueen(board, 0)
return self.result
def placeQueen(self, board: List[List[str]], row: int):
if (row >= len(board)):
self.result.append(self.convertBoardToResult(board))
return
for col in range(len(board[0])):
if self.canPlaceQueen(row, col):
board[row][col] = App.QUEEN
self.occupiedColumn.add(col)
self.occupiedBackwardDiagonal.add(col - row)
self.occupiedForwardDiagonal.add(row + col)
self.placeQueen(board, row + 1)
board[row][col] = App.EMPTY
self.occupiedColumn.remove(col)
self.occupiedBackwardDiagonal.remove(col - row)
self.occupiedForwardDiagonal.remove(row + col)
def canPlaceQueen(self, row: int, col: int) -> bool:
isOccupied = col in self.occupiedColumn or (col - row) in self.occupiedBackwardDiagonal or (row + col) in self.occupiedForwardDiagonal
return not isOccupied
def convertBoardToResult(self, board: List[List[str]]) -> List[str]:
result = []
for row in board:
result.append("".join(row))
return result
Time complexity: $O(n^2)$
Space complexity: $O(n^2)$