Statement#

Given a chessboard of size n×nn \times n, determine how many ways nn queens can be placed on the board, such that no two queens attack each other.

A queen can move horizontally, vertically, and diagonally on a chessboard. One queen can be attacked by another queen if both share the same row, column, or diagonal.

Constraints:

  • 1n91 \leq n \leq 9

Solution#

So far, you’ve probably brainstormed some approaches and have an idea of how to solve this problem. Let’s explore some of these approaches and figure out which one to follow based on considerations such as time complexity and any implementation constraints.

Naive solution#

In order to find the optimal placement of the queens on a chessboard, we could find all configurations with all possible placements of nn queens and then determine for every configuration if it is valid or not.

However, this would be very expensive, since there would be a very large number of possible placements and only a handful of valid ones. For example, when trying to place 66 queens on a 6×66 \times 6 chessboard using brute force, suppose we place the first two queens side by side on the first two squares, there still remain 34×33×32×31=111302434 \times 33 \times 32 \times 31 = 1113024 possible ways to place the remaining 44 queens. All of these ways are invalid since placing two queens next to each other is invalid. The time complexity of this solution would be O((n2n))O(\binom{n^2}{n}) and the space complexity would be O(n)O(n), where nn is the dimension of the chessboard.

Optimized solution using backtracking#

A better and more optimized approach would be to only check for valid placements. This can be achieved by backtracking to a previously valid state in case there is no safe move left. Through backtracking, we avoid an exhaustive search and thus improve the algorithm’s performance. Therefore, this problem is a great fit for the backtracking pattern.

Problem analysis#

As stated in the problem statement, we have an n×nn \times n chessboard to place the queens such that no two queens attack each other. Let’s understand the implications of these two conditions:

  1. Once we place the first queen anywhere in the first row, no other queen may be placed in that row. Therefore, the search for a safe position for the next queen starts from the next row. This gives us a simple means to store a solution: we simply need to store the column for each row, that is, a list of nn column indices, each representing a safe placement.
  2. As there are nn rows in the given board, and each row may safely hold just one queen, in any valid solution, all nn rows would be used, each holding exactly one queen. This gives us both a condition to check the validity of a solution, as well as a way to check whether a solution is complete.

Solution 1#

The backtracking algorithm to solve the nn-queens problem is very similar to a depth-first search of a tree.

There are two conditions that cause us to backtrack, but for two different purposes:

  • When we find that we cannot place the current queen in a particular row, we have to backtrack and alter the position of the queen whose position was decided before the current one. Next, we move forward again to find a safe position for the current queen.
  • Once we find a valid solution, we still have to identify all the other valid solutions. So, we backtrack by removing the last queen placed on the board and resuming our search for solutions from that point. In order to be sure to find all possible solutions, we’ll need to backtrack, row by row, all the way back to the first queen placed on the board, changing its position and then looking for alternative solutions.

Let’s run our example on a 4×44 \times 4 board with 44 queens:

Created with Fabric.js 3.6.6 X We will consider the bottom row as the first row, and the left mostcolumn as the first column. We'll place the first queen in the first column of the first row.

1 of 12

Created with Fabric.js 3.6.6 In the second row we can't place the second queen in the first two columns. So we'll place her in the third column. X X

2 of 12

Created with Fabric.js 3.6.6 X X In the third row there is no column where we can place the third queen. Regardless of where we try to place a queen in the third row, it is attacked by one of the two queens already placed on board.

3 of 12

Created with Fabric.js 3.6.6 So we'll backtrack to the second row and place the second queen at a new location, that is, the fourth column. X X

4 of 12

Created with Fabric.js 3.6.6 X X X Now, in the third row, we can't place a queen in the first column but we can place a queen in the second column.

5 of 12

Created with Fabric.js 3.6.6 X X X Now in the fourth row there is no column where a queen can be placed. Each column in the fourth row is open to attack by existing queens on the board.

6 of 12

Created with Fabric.js 3.6.6 We'll backtrack to the third row, but we find that the next two columns (the third and the fourth) are both open to attack by the first two queens. X X X

7 of 12

Created with Fabric.js 3.6.6 We'll backtrack to the second row where we had already placed the second queen in the last column. Since no solution can be found based on this configuration, we have to backtrack to the first row. X X

8 of 12

Created with Fabric.js 3.6.6 X We have exhausted all the possibilities stemming from placing the first queen in the first column of the second row. So, we move it to the second column of the second row.

9 of 12

Created with Fabric.js 3.6.6 X X In the second row we can't place the second queen in any of the first three columns, so we'll place the second queen in the fourth column of the second row.

10 of 12

Created with Fabric.js 3.6.6 X X X In the third row, we can place the third queen in the first column as this column cannot be attacked by the first two queens.

11 of 12

Created with Fabric.js 3.6.6 X X X X In the fourth row, the queen can't be placed in the first two columns, but can be placed in the third column. We have placed all four queens on the board, one on each row, so this is one complete, valid solution. Similarly, from here we can backtrack to previous rows and repeat the same steps to find more solutions.

12 of 12

Note: In the following section, we will gradually build the solution. Alternatively, you can skip straight to just the code.

Step-by-step solution construction#

The algorithm proceeds through the following steps:

  1. Place a queen in the first column of the first row.

  2. Now place a queen in the first such column of the second row where placement is permissible. This means that the current queen is not attacked by any queen already on the board.

  3. If no such column is found, we’ll backtrack to the previous row and try to place the queen in the next column of that row.

  4. We continue this until we reach the last row of the board.

  5. When we are able to successfully place the nthn^{th} queen in the nthn^{th} row, we’ll have found one valid solution.

  6. After we find a solution, we backtrack to the previous row to find the next solution. Next, we try to find another column in the previous row where placement is permissible.

We’ll start by declaring a results array that will store all possible solutions for nn queens, and a solution array that will keep track of the current solution. We’ll also implement a is_valid_move() function that checks whether the desired move can place a queen at a safe position. A move is valid if the queen is not vulnerable to attack from other queens on the board.

Check if a move is valid

Next, we’ll call a recursive function called solve_n_queens_rec() to place the queens on the chessboard. We’ll start by placing the queen in the first column of the first row. For every placement, we’ll check if a move is valid. If yes, we’ll add it to our solution.

Placing the queens

Once our queen is placed at the correct position, we’ll recursively check if another queen can be safely placed in the next row. If yes, we’ll add the position to our solution and move to the next queen. Else, we’ll backtrack to the previous valid position and try a different configuration.

If we successfully reach the last row of the chessboard, we’ll save that solution in the results array and backtrack to find the alternatives.

N-Queens
Just the code#

Here’s the complete solution to this problem:

N-Queens
Solution summary#

To recap, the solution to this problem can be divided into the following parts:

  1. Place a queen in the first column of the first row.
  2. Place a queen wherever permissible in the next row.
  3. Backtrack if no safe configuration exists.
  4. Once a solution is found, backtrack to find other possible configurations.
Time complexity#

The recurrence relation for the time complexity of this solution is:

T(n)=nT(n1)+O(n2).T(n)=nT(n-1)+O(n^2).

Deriving a precise upper bound for this recurrence relation is beyond the scope of this course. However, we can prove by induction that the time complexity, given this recurrence relation, is no worse than O(n!)O(n!).

Space complexity#

The space complexity of this solution is O(n)O(n), where nn is the dimension of the chessboard. This is because the maximum number of calls to the recursive worker function is nn, one for each row, and each call takes up O(1)O(1) space on the call stack.

Solution 2#

We may also implement an iterative form of the backtracking search function, using a stack to keep track of the current solution. The stack holds only the column values and one solution is stored in the stack at a time. When one solution is complete, we save the solution present in the stack to a separate list of valid solutions. We then backtrack by popping from the stack and resuming the search for the next solution.

Let’s run the above example using this solution and see how backtracking is achieved using the stack:

Created with Fabric.js 3.6.6 top stack Initial state of the chessboard and stack. 3 2 1 0 0 1 2 3

1 of 28

Created with Fabric.js 3.6.6 3 2 1 0 X 0 1 2 3 top 0 stack stack.push(0)

2 of 28

Created with Fabric.js 3.6.6 3 2 1 X 0 X 0 1 2 3 top 2 0 stack stack.push(2)

3 of 28

Created with Fabric.js 3.6.6 top 3 0 stack We backtracked as no valid move could be found with the second queen in column 3.stack.pop()stack.push(3) 3 2 1 X 0 X 0 1 2 3

4 of 28

Created with Fabric.js 3.6.6 stack.push(1) 3 2 X 1 X 0 X 0 1 2 3 top 1 3 0 stack

5 of 28

Created with Fabric.js 3.6.6 3 2 1 X 0 X 0 1 2 3 top 3 0 stack There is no valid move in the fourth row so backtrack.stack.pop()

6 of 28

Created with Fabric.js 3.6.6 top 0 stack There is no valid move in the remaining columns of third row so backtrack.stack.pop() 3 2 1 0 X 0 1 2 3

7 of 28

Created with Fabric.js 3.6.6 3 2 1 0 X 0 1 2 3 top 1 stack There are no more columns in the second rowso backtrack. stack.pop()stack.push(1)

8 of 28

Created with Fabric.js 3.6.6 stack.push(3) 3 2 1 X 0 X 0 1 2 3 top 3 1 stack

9 of 28

Created with Fabric.js 3.6.6 stack.push(0) 3 2 X 1 X 0 X 0 1 2 3 top 0 3 1 stack

10 of 28

Created with Fabric.js 3.6.6 stack.push(2)As row == 4, so this is one solution. We'll backtrack from here to find the next solution. 3 X 2 X 1 X 0 X 0 1 2 3 top 2 0 3 1 stack

11 of 28

Created with Fabric.js 3.6.6 3 2 X 1 X 0 X 0 1 2 3 top 0 3 1 stack Backtrack to find the next solution. stack.pop()

12 of 28

Created with Fabric.js 3.6.6 top 3 1 stack There is no valid move in the remaining columns of the fourth row so backtrack.stack.pop() 3 2 1 X 0 X 0 1 2 3

13 of 28

Created with Fabric.js 3.6.6 top 1 stack There is no valid move in the remaining columns of the third row so backtrack.stack.pop() 3 2 1 0 X 0 1 2 3

14 of 28

Created with Fabric.js 3.6.6 There is no valid move in the remaining columns of thesecond row so backtrack to the first row and change theposition of the first queen. stack.pop()stack.push(2) top 2 stack 3 2 1 0 X 0 1 2 3

15 of 28

Created with Fabric.js 3.6.6 3 2 1 X 0 X 0 1 2 3 top 0 2 stack Place the queen in the first column of the second row.stack.push(0)

16 of 28

Created with Fabric.js 3.6.6 3 2 X 1 X 0 X 0 1 2 3 Place the queen in the last column of the third row.stack.push(3) top 3 0 2 stack

17 of 28

Created with Fabric.js 3.6.6 3 X 2 X 1 X 0 X 0 1 2 3 top 1 3 0 2 stack stack.push(1)As row == 4, so this is the second solution. We'll backtrackfrom here to find the next solution.

18 of 28

Created with Fabric.js 3.6.6 3 2 X 1 X 0 X 0 1 2 3 top 3 0 2 stack Backtrack to find the next solution.stack.pop()

19 of 28

Created with Fabric.js 3.6.6 3 2 1 X 0 X 0 1 2 3 top 0 2 stack There is no valid move in remaining columns of fourthrow so backtrack.stack.pop()

20 of 28

Created with Fabric.js 3.6.6 3 2 1 0 X 0 1 2 3 top 2 stack There is no column remaining in the third row sobacktrack.stack.pop()

21 of 28

Created with Fabric.js 3.6.6 3 2 1 0 X 0 1 2 3 top 3 stack There is no valid move in the remaining columnsof second row so backtrack.stack.pop()stack.push(3)

22 of 28

Created with Fabric.js 3.6.6 stack.push(0) 3 2 1 X 0 X 0 1 2 3 top 0 3 stack

23 of 28

Created with Fabric.js 3.6.6 stack.push(2) 3 2 X 1 X 0 X 0 1 2 3 top 2 0 3 stack

24 of 28

Created with Fabric.js 3.6.6 3 2 1 X 0 X 0 1 2 3 top 0 3 stack There is no valid move in any column of fourthrow so backtrack.stack.pop()

25 of 28

Created with Fabric.js 3.6.6 3 2 1 X 0 X 0 1 2 3 top 1 3 stack There is no valid move in remaining columns of third row so backtrack.stack.pop()stack.push(1)

26 of 28

Created with Fabric.js 3.6.6 3 2 1 0 X 0 1 2 3 top 3 stack There is no valid move in any column of thirdrow so backtrack.stack.pop()

27 of 28

Created with Fabric.js 3.6.6 3 2 1 0 0 1 2 3 top stack There is no valid move in remaining columns of second row so backtrack.stack.pop()Stack is empty so stop here. No more valid solutions exist.

28 of 28

Solution summary#

To recap, the solution to this problem can be divided into the following parts:

  1. Place a queen in the first column of the first row.
  2. Use a stack to keep track of the current solution.
  3. Place a queen wherever permissible in the next row.
  4. Backtrack by popping from the stack to find the next solution.
Time complexity#

The time complexity of this solution is O(n!)O(n!), where nn is the dimension of the chessboard.

Space complexity#

The space complexity of this solution is O(n)O(n), where nn is the dimension of the chessboard.

N-Queens

Subsets: Introduction