【Daily Interview】- 20 N 皇后

475 阅读2分钟

题目

img01

题目来源:N 皇后 - 力扣

分析

这道题的原型是大名鼎鼎的八皇后问题,事实上,除了暴力枚举以外,也很难有太好的方法来处理这个问题。而说到暴力枚举,回溯算法可以说是非常优秀的方法,那么这里我们用回溯的角度来思考一下应该如何完成这个问题。

下面以 4 x 4 的棋盘为例:

img02

首先要知道皇后的攻击范围:

  • 同行
  • 同列
  • 同一条对角线

以坐标 [1, 1],即 2 号位置为例,皇后攻击范围如图所示:

img03

这些处于攻击范围的格子,它们有什么特点呢(设行为 row,列为 col)?

  • row 相等
  • col 相等
  • row + col 相等
  • row - col 相等

结合上述几点,我们可以尝试遍历地图,并且找到不可下的位置:

function find(row, chessboard = []) {
  for (let col = 0; col < n; col++) {
    const cantSet = chessboard.some((ci, ri) => 
		  ri === row || ci === col || ri - ci === row - col || ri + ci === row + col
    );
    if (cantSet) continue;
    find(row + 1, [...chessboard, col]);
  }
}

其中值得注意的有:

  • chessboard 中索引值为行,值为列
  • ci, ri 是之前摆放棋子的行列
  • col, row 为当前所在位置的索引

上述代码是如何完成回溯的呢?

其实仔细观察不难发现,当所有位置都不可下之后,循环会直接结束,回到上一层,即完成回溯,具体可以把值打印出来看看。

img04

可见,当发现第二行所有位置都不能下载的时候,程序回退到了上一行继续进行尝试,到了这一步,整个查找基本已经完成,接下来就需要设置终止条件,相对来说,这个还是比较简单的:

  • 什么时候终止呢?自然是 row === n 的时候,因为在 row === (n - 1) 的时候,就已经是在找最后一行了。

而在终止的时候的操作也很简单,将棋盘转换成符合标准的形式,push 进结果集即可:

if (row === n) {
  result.push(
    chessboard.map(c => {
      let arr = new Array(n).fill(".");
      arr[c] = "Q";
      return arr.join("");
    })
  );
}

最后将结果集返回即可:

var solveNQueens = function (n) {
  const result = [];
  find(0);
  return result;
};

结果如下:

img05