这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
前言
关于 LeetCode 数组类型题目的相关解法,可见LeetCode 数组类型题目做前必看,分类别解法总结了题目,可以用来单项提高。觉得有帮助的话,记得多多点赞关注哦,感谢!
题目描述
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。(皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。)
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1
输出:[["Q"]]
链接:leetcode-cn.com/problems/n-…
题解
这道题目和LeetCode 37 数独是类似的, 都是采用 dfs+回溯 的方法来填充空格.
本题解法有几个重点.
一个是标记数组: 共需要三个标记数组, col, diag1, diag2, 分别表示列, 红色对角线, 蓝色对角线, 如果数组对应 index 值为 1, 表示第 index 列/对角线上放置了皇后, 该位置不能再放置皇后. 这里不需要对行进行标记, 因为我们是从上到下进行遍历的, 行肯定是不会重复放置皇后的. 同时对于 diag1 和 diag2 来说, 在斜对角线上的元素坐标有以下的特点, 一共各有 2n - 1 个斜线; 红对角线上的元素 i + j 即行 index 和列 index 相加的值是一样的; 蓝色对角线上元素 i - j 的值是一样的, 因为 i - j 会存在负数, 为了便于用数组的下标表示, 用 i - j + n - 1 来对应在 diag2 的下标.
(图片来源于 花花酱)
第二个是递归终止条件, 和 leetcode 37 题一致, 当遍历到行数超过 n 时, 说明 0 - n-1 行已经填好了.
第三个是回溯, 在递归调用之后回溯时, 要将棋盘还原, 方便后续的递归调用.
具体代码见下方
时间复杂度 O(n!), 因为第一行有 n 种情况可以选择, 第二行有 n - 1种,..., 因此 n 行一共需要遍历判断 n! 种情况.
空间复杂度 O(n). 递归深度最高为 n.
/**
* @param {number} n
* @return {string[][]}
*/
var solveNQueens = function(n) {
let res = []
let board = Array.from({ length: n }, () => (new Array(n).fill('.')))
let col = new Array(n).fill(0)
let diag1 = new Array(2 * n - 1).fill(0)
let diag2 = new Array(2 * n - 1).fill(0)
// 判断该位置是否能够放置皇后
const available = (x, y) => {
return !col[y] && !diag1[x + y] && !diag2[x - y + n - 1]
}
// 放置皇后或者恢复棋盘
const update = (x, y, input) => {
col[y] = input
diag1[x + y] = input
diag2[x - y + n - 1] = input
board[x][y] = input ? 'Q' : '.'
}
const dfs = (x) => {
// 递归终止条件
if (x === n) {
res.push(board.map(row => row.join('')))
return
}
// 从 0 行开始填充棋盘
for (let i = 0; i < n; ++i) {
if (available(x, i)) {
update(x, i, 1)
dfs(x + 1)
// 回溯
update(x, i ,0)
}
}
}
dfs(0)
return res
};