Reputation: 4993
I think I understand the point of the NQueens algorithm - you check each row for available spaces and if they exist, put a queen there, and recursively call the algorithm to the next row. Here is my algorithm (with N size 3)
from typing import List
import pdb
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
self.solutions = 0
self.attack_zone = [[0]*n for i in range(n)]
self.size = n
self.backtrack_nqueens(0,0) #start on row 0
return self.solutions
def backtrack_nqueens(self,row,iteration):
#starting at row
for col in range(self.size):
if self.is_not_under_attack(row,col):
print("on iteration",iteration,row,col,"was a safe place for some reason")
#adds queen to this row
self.place_or_remove_queen(row,col,True)
if row + 1 == self.size:
## THIS SHOULD NEVER HAPPEN
self.solutions += 1
else:
self.backtrack_nqueens(row+1,iteration+1)
#removes queen from this row
self.place_or_remove_queen(row,col,False)
def place_or_remove_queen(self,row,col,place):
flag = 1 if place else 0
self.attack_zone[row] = [flag]*self.size
#for col
for r in range(self.size):
self.attack_zone[r][col] = flag
#lower right
for r,c in zip(range(row,self.size,1),range(col,self.size,1)):
self.attack_zone[r][c] = flag
#upper right
for r,c in zip(range(row,-1,-1),range(col,self.size,1)):
self.attack_zone[r][c] = flag
#lower left
for r,c in zip(range(row,self.size,1),range(col,-1,-1)):
self.attack_zone[r][c] = flag
#upper left
for r,c in zip(range(row,-1,-1),range(col,-1,-1)):
self.attack_zone[r][c] = flag
def is_not_under_attack(self,row,col):
return self.attack_zone[row][col] == 0
s = Solution()
print(s.solveNQueens(3))
When I run this, self.solutions ends up at 3 - basically it finds 3 places for a queen which we all know shouldn't happen. I don't understand how its getting to that row where I said ##THIS SHOULD NEVER HAPPEN.
The only thing I can think of is that I am somehow removing a queen that shouldn't be removed? So my attack_zone has empty spaces when it shouldn't. Can someone point out what I am doing wrong in the recursion that would cause this and why?
Upvotes: 4
Views: 124
Reputation: 36289
The problem is that when you remove a queen, you mark the corresponding squares as "free", i.e. setting their "threat count" to zero, regardless of whether another queen also threatens that square. Considering your example the following happens:
# Iteration 1 # Iteration 2 # Iteration 3
| 1 | 1 | Q | | 1 | 1 | Q | | 0 | 0 | Q |
+---+---+---+ +---+---+---+ +---+---+---+
| 0 | 1 | 1 | | Q | 1 | 1 | | 0 | 0 | 0 |
+---+---+---+ +---+---+---+ +---+---+---+
| 1 | 0 | 1 | | 1 | 1 | 1 | | 0 | 0 | 1 |
On iteration #3 the queen on (1, 0)
is removed and all corresponding squares are flagged as 0
, also the ones that are actually threatened by the queen on (0, 2)
. The same happens for queens on (1, 1)
and (1, 2)
and the latter eventually leads to a free square on (2, 0)
which is the last row.
In order to make the algorithm work you can maintain a "threat count" which tracks by how many queens a square is threatened. Placing a queen increases that count by one and removing a queen reduces it by one. You can realize this by changing the value of flag
to flag = 1 if place else -1
and then use self.attack_zone[r][c] += flag
everywhere instead of self.attack_zone[r][c] = flag
. This gives then the following picture:
# Iteration 1 # Iteration 2 # Iteration 3
| 1 | 1 | Q | | 2 | 2 | Q | | 1 | 1 | Q |
+---+---+---+ +---+---+---+ +---+---+---+
| 0 | 1 | 1 | | Q | 2 | 2 | | 0 | 1 | 1 |
+---+---+---+ +---+---+---+ +---+---+---+
| 1 | 0 | 1 | | 2 | 1 | 1 | | 1 | 0 | 1 |
Upvotes: 5