面试题 08.12. 八皇后

面试题 08.12. 八皇后

难度困难78收藏分享切换为英文接收动态反馈

设计一种算法,打印 N 皇后在 N × N 棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线。

注意:本题相对原题做了扩展

示例:

 输入:4
 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
 解释: 4 皇后问题存在如下两个不同的解法。
[
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]

 

前言
「NN 皇后问题」研究的是如何将 NN 个皇后放置在 N \times NN×N 的棋盘上,并且使皇后彼此之间不能相互攻击。

皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。

直观的做法是暴力枚举将 NN 个皇后放置在 N \times NN×N 的棋盘上的所有可能的情况,并对每一种情况判断是否满足皇后彼此之间不相互攻击。暴力枚举的时间复杂度是非常高的,因此必须利用限制条件加以优化。

显然,每个皇后必须位于不同行和不同列,因此将 NN 个皇后放置在 N \times NN×N 的棋盘上,一定是每一行有且仅有一个皇后,每一列有且仅有一个皇后,且任何两个皇后都不能在同一条斜线上。基于上述发现,可以通过回溯的方式寻找可能的解。

回溯的具体做法是:使用一个数组记录每行放置的皇后的列下标,依次在每一行放置一个皇后。每次新放置的皇后都不能和已经放置的皇后之间有攻击:即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上,并更新数组中的当前行的皇后列下标。当 NN 个皇后都放置完毕,则找到一个可能的解。当找到一个可能的解之后,将数组转换成表示棋盘状态的列表,并将该棋盘状态的列表加入返回列表。

由于每个皇后必须位于不同列,因此已经放置的皇后所在的列不能放置别的皇后。第一个皇后有 NN 列可以选择,第二个皇后最多有 N-1N−1 列可以选择,第三个皇后最多有 N-2N−2 列可以选择(如果考虑到不能在同一条斜线上,可能的选择数量更少),因此所有可能的情况不会超过 N!N! 种,遍历这些情况的时间复杂度是 O(N!)O(N!)。

为了降低总时间复杂度,每次放置皇后时需要快速判断每个位置是否可以放置皇后,显然,最理想的情况是在 O(1)O(1) 的时间内判断该位置所在的列和两条斜线上是否已经有皇后。

以下两种方法分别使用集合和位运算对皇后的放置位置进行判断,都可以在 O(1)O(1) 的时间内判断一个位置是否可以放置皇后,算法的总时间复杂度都是 O(N!)O(N!)。

方法一:基于集合的回溯
为了判断一个位置所在的列和两条斜线上是否已经有皇后,使用三个集合 columns、diagonals1 diagonals2 

​    
  分别记录每一列以及两个方向的每条斜线上是否有皇后。

列的表示法很直观,一共有 NN 列,每一列的下标范围从 00 到 N-1N−1,使用列的下标即可明确表示每一列。

如何表示两个方向的斜线呢?对于每个方向的斜线,需要找到斜线上的每个位置的行下标与列下标之间的关系。

方向一的斜线为从左上到右下方向,同一条斜线上的每个位置满足行下标与列下标之差相等,例如 (0,0)(0,0) 和 (3,3)(3,3) 在同一条方向一的斜线上。因此使用行下标与列下标之差即可明确表示每一条方向一的斜线。

面试题 08.12. 八皇后_第1张图片

方向二的斜线为从右上到左下方向,同一条斜线上的每个位置满足行下标与列下标之和相等,例如 (3,0)(3,0) 和 (1,2)(1,2) 在同一条方向二的斜线上。因此使用行下标与列下标之和即可明确表示每一条方向二的斜线。

面试题 08.12. 八皇后_第2张图片

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

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/n-queens/solution/nhuang-hou-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:

        def generateBoard():
            board = list()
            for i in range(n):
                row[queens[i]] = "Q"
                board.append("".join(row))
                row[queens[i]] = "."
            return board


        def backtrack(row:int):
            if row == n:
                board = generateBoard()
                res.append(board)
            else:
                for i in range(n):
                    if i in columns or row-i in diagonal1 or row+i in diagonal2:
                        continue
                    else:
                        queens[row] = i
                        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)
        res = list()
        columns = set()
        diagonal1 = set()
        diagonal2 = set()
        queens = [-1]*n
        row = ["."] * n
        backtrack(0)
        return res

复杂度分析

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

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

 

你可能感兴趣的:(#,回溯,#,递归,#,动态规划,python,算法,leetcode)