这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
95. N 皇后 (n-queens)
标签
- DFS + 回溯
- 困难
题目
这里不贴题了,leetcode打开就行,题目大意:
经典的 n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。(国际象棋中皇后是可以横,纵,对角线 不限步长地
走,所以是最厉害的子了)
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案
。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
能看出来吧,这些皇后互相都不能攻击,也就是攻击返回不重合。
基本思路
对基本DFS + 回溯
不熟悉地同学移步 DFS + 回溯
这个解是参考这篇 我觉得他的图解非常清晰。
其中难点:对角线和差的关系我们用下图能清晰看出 i + j === row + col || i - j === row - col
就表示和在对角线上。
a00 a01 a02 a03 0 -1 -2 -3 0 1 2 3
a10 a11 a12 a13 i-j 1 0 -1 -2 i+j 1 2 3 4
a20 a21 a22 a23 2 1 0 -1 2 3 4 5
a30 a31 a32 a33 3 2 1 0 3 4 5 6
写法实现
const solveNQueens = (n) => {
let res = []
// 棋盘初始化,每个位置暂时都为空 就是 '.'
const board = new Array(n).fill(0).map(item => new Array(n).fill('.'))
// 判断是否合法位置函数,保证皇后之间不冲突
const isValidPosition = (row, col) => {
// 之前每一行都要扫描
for (let i = 0; i < row; i++) {
// 遍历所有的列
for (let j = 0; j < n; j++) {
// 如果该位置有 Q 且 同列 或 在对角线上, 则不合法
// 同行 在这里不判断 是因为我们是一行行来放,所以不会同行
if (board[i][j] === 'Q'
&& (j === col || i + j === row + col || i - j === row - col)) {
return false;
}
}
}
return true;
}
// 又到了我们的 DFS + 回溯环节
const dfs = (row) => {
// 放置当前行
if (row === n) {
// 递归出口 已经超过最后一行,保存当前盘
let boardBackup = board.slice()
let tempRes = boardBackup.map(item => item.join(''))
res.push(tempRes);
return
}
// 开始遍历这一行的每一列的位置
for (let col = 0; col < n; col++) {
// 如果是合法位置,就放下 Q
if (isValidPosition(row, col)) {
board[row][col] = "Q";
// 递归 DFS 下一行
dfs(row + 1);
// 回溯,撤销选择
board[row][col] = '.';
}
}
}
// 从 0 行开始
dfs(0)
return res
}
console.log(solveNQueens(4))
优化实现
最好是用三个数组或 Set 去记录出现过皇后的列们、正对角线们、反对角线们,用空间换取时间。
const solveNQueens = (n) => {
let res = []
// 棋盘初始化,每个位置暂时都为空 就是 '.'
const board = new Array(n).fill(0).map(item => new Array(n).fill('.'))
// 判断是否合法位置函数,保证皇后之间不冲突
// 记录已经被占据的列,对角线集合
const occupiedCols = new Set() // 列
const occupiedDiags = new Set() // 对角线
const occupiedReverseDiags = new Set() // 反对角线
// 又到了我们的 DFS + 回溯环节
const dfs = (row) => {
// 放置当前行
if (row === n) {
// 递归出口 已经超过最后一行,保存当前盘
let boardBackup = board.slice()
let tempRes = boardBackup.map(item => item.join(''))
res.push(tempRes);
return
}
// 开始遍历这一行的每一列的位置
for (let col = 0; col < n; col++) {
// 如果当前点的所在的列,所在的对角线都没有皇后,即可选择,否则,跳过这行
if (!occupiedCols.has(col)
&& !occupiedDiags.has(row - col)
&& !occupiedReverseDiags.has(row + col)) {
// 可放置 Q
board[row][col] = 'Q';
occupiedCols.add(col)
occupiedDiags.add(row - col)
occupiedReverseDiags.add(row + col)
// 进行下一行
dfs(row + 1)
// 回溯
board[row][col] = '.';
occupiedCols.delete(col)
occupiedDiags.delete(row - col)
occupiedReverseDiags.delete(row + col)
}
}
}
// 从 0 行开始
dfs(0)
return res
}
console.log(solveNQueens(4))
另外向大家着重推荐下这位大哥的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦
搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the rever monster
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧