前端算法面试必刷题系列[50]

267 阅读3分钟

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

95. N 皇后 (n-queens)

标签

  • DFS + 回溯
  • 困难

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

经典的 n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。(国际象棋中皇后是可以横,纵,对角线 不限步长地走,所以是最厉害的子了)

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

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

示例 1:

image.png

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

能看出来吧,这些皇后互相都不能攻击,也就是攻击返回不重合。

基本思路

对基本DFS + 回溯不熟悉地同学移步 DFS + 回溯

这个解是参考这篇 我觉得他的图解非常清晰。

image.png

其中难点:对角线和差的关系我们用下图能清晰看出 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,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考