这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战
题目描述
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回 n 皇后不同的解决方案的数量。
示例:
输入: 4
输出: 2
解释: 4 皇后问题存在如下两个不同的解法。
[
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
提示:
- 皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一或
N-1步,可进可退。(引用自百度百科 - 皇后)
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/n-… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
基于集合判重的回溯算法
国际象棋中,王的女人很厉害,横向、纵向、斜向都能攻击到,现在要为 N 个后宫佳丽搭台唱戏,为了保证后宫安定,需要满足以下四个条件:
- 每一行只能有一位皇后
- 每一列只能有一位皇后
- 左上到右下的主对角线方向只能有一位皇后
- 右上到左下的副对角线方向只能有一位皇后
由于我们可以在回溯时控制每行放置一位皇后,放完进入下一行皇后的放置流程,所以一行只会出现一个皇后,第一个条件天然满足,只需要保证后三个条件满足即可。
可以使用名叫 cols、d1 和 d2 的三个集合来分别记录之前已经用过的列、主对角线、副对角线,新放置皇后的列数、主对角线和副对角线不能在 cols、d1、d2 中出现。
cols 保存之前已经用过的列数很好理解,直接存 0 到 n - 1 即可。但是主对角线和副对角线是怎么存的?
通过观察我们发现:
- 位于同一条主对角线上的所有元素,它们的行数和列数之差相等,存
row - col即可 - 位于同一条副对角线上的所有元素,它们的行数和列数之和相等,存
row + col即可
到这里,思路基本就出来了,放出最终解法之前先对照一下我的回溯算法模板:
def backtrack(path, state, opts):
if base: # 基线条件
res.append(path)
return
for opt in opts:
if prune: # 剪枝条件
continue/break
# 保存现场(做出选择)
path.append(opt)
state = change(state)
opts.remove(opt)
# 递归
backtrack(path, state, opts)
# 恢复现场(撤销选择)
path.remove(opt)
state = unchange(state)
opts.append(opt)
对于本题:
path路径是不需要的,因为不用记录棋盘摆放,最终只是要统计数字而已state状态是需要的,用row来记录当前要放置是第几行opts选择集也是需要的,它是由cols、d1和d2三个集合决定的
下面是本题参考解法:
class Solution:
def totalNQueens(self, n: int) -> int:
res = 0
cols, d1, d2 = set(), set(), set()
def backtrack(row):
# 基线条件
if row == n:
nonlocal res
res += 1
for col in range(n):
# 剪枝条件
if (col in cols
or row - col in d1
or row + col in d2):
continue
# 保存现场
cols.add(col)
d1.add(row - col)
d2.add(row + col)
# 递归
backtrack(row + 1)
# 恢复现场
cols.remove(col)
d1.remove(row - col)
d2.remove(row + col)
return backtrack(0) or res
运行结果:
执行结果:通过
执行用时:56 ms, 在所有 Python3 提交中击败了79.96% 的用户
内存消耗:13.5 MB, 在所有 Python3 提交中击败了9.69% 的用户
本题的时间复杂度是 ,一共有 n 层递归,在不考虑斜向的情况下,每层递归可以选择的列数量分别是 n, n - 1, n - 2, ..., 1,所以最多有 种组合,于是时间复杂度为 。
另外,本题还可以基于位运算来做,可以参考官方题解的方法二。