深度优先搜索、回溯思想

题目

n皇后问题研究的是如何将n个皇后放置在$n\times n$的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数n,返回所有不同的n皇后问题的解决方案。

每一种解法包含一个不同的n皇后问题的棋子放置方案,该方案中‘Q’‘.’分别代表了皇后和空位

示例 1:

image.png

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例2:

输入:n = 1
输出:[["Q"]]

题解

简要分析:我们首先可以知道不能相互攻击也即

  • 同一行,同一列只能有一个皇后
  • 且任何两个皇后都不能在同一条斜线上

我们可以使用回溯法求解

第一个皇后有N个选择,第二个则有N-1个选择,依次推得所有可能的情况不会超过 N! 种,遍历这些情况的时间复杂度是 O(N!)。

方法一:基于集合的回溯

判断一个位置所在的列和两条斜线上是否存在皇后,使用三个集合columns、diagonals1和diagonals2分别记录每一列以及两条斜线上是否存在皇后。

  • 列总共有N列,每列下标从0到N-1,使用列的下标可明确表示每一列
  • 从左上至右下方向斜线,位置满足:行下标与列下标之差相等
  • 从左下至右上方向斜线,位置满足:行下标与列下标之和相等

每次放置皇后时,对于每个位置判断其是否在三个集合中,如果三个集合都不包含当前位置,则当前位置是可以放置皇后的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution:
def solveNQueens(self, n):# 返回:List[List[str]], n:int
# 生成一个棋盘 ,queens存放每一列的Q
def generateBoard():
board = list()
for i in range(n):
row[queens[i]] = "Q" # queens中记录了每一行中对应Q的列位置
board.append("".join(row)) # 将每行情况添加至board
row[queens[i]] = "." # 还原row,继续下一行
return board

def backtrack(row: int):
if row == n: # 遍历完行,递归结束
board = generateBoard() #此时生成带有Q的棋盘
solutions.append(board) # 将方案添加至solutions列表中
else:
for i in range(n):
# 如果对当前行的每一列在三个存放后的集合中,则继续在列中找
if i in columns or row - i in diagonal1 or row + i in diagonal2:
continue
queens[row] = i # 没在上述三个集合中,则添加Q
columns.add(i) # 在上述三个集合中,添加位置坐标
diagonal1.add(row - i)# 左上至右下斜线
diagonal2.add(row + i)# 左下至右上斜线
backtrack(row + 1) # 递归下一行
columns.remove(i) # 回溯
diagonal1.remove(row - i)
diagonal2.remove(row + i)

solutions = list()
queens = [-1] * n
columns = set()
diagonal1 = set()
diagonal2 = set()
row = ["."] * n
backtrack(0)
return solutions
1
2
a= Solution()
a.solveNQueens(4)
[['.Q..', '...Q', 'Q...', '..Q.'], ['..Q.', 'Q...', '...Q', '.Q..']]

复杂度分析

时间复杂度:O(N!),其中 N 是皇后数量。

空间复杂度:O(N),其中 N 是皇后数量。空间复杂度主要取决于递归调用层数、记录每行放置的皇后的列下标的数组以及三个集合,递归调用层数不会超过 N,数组的长度为 N,每个集合的元素个数都不会超过 N。