回溯算法解决 N 皇后问题

163 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

回溯算法解决 N 皇后问题

1. N 皇后

1.1 问题描述

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

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

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

1.2 要求

示例 1:

img

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

示例 2:

输入:n = 1
输出:[["Q"]]

1.3 思路

皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。

直观的做法是暴力枚举将 N 个皇后放置在 N×N 的棋盘上的所有可能的情况,并对每一种情况判断是否满足皇后彼此之间不相互攻击。暴力枚举的时间复杂度是非常高的,因此必须利用限制条件加以优化。

显然,每个皇后必须位于不同行和不同列,因此将 N 个皇后放置在 N×N 的棋盘上,一定是每一行有且仅有一个皇后,每一列有且仅有一个皇后,且任何两个皇后都不能在同一条斜线上。基于上述发现,可以通过回溯的方式寻找可能的解。

回溯的具体做法是:使用一个数组记录每行放置的皇后的坐标,依次在每一行放置一个皇后。每次新放置的皇后都不能和已经放置的皇后之间有攻击:即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上,并更新数组中的当前行的皇后坐标。当 N 个皇后都放置完毕,则找到一个可能的解。当找到一个可能的解之后,将数组转换成表示棋盘状态的列表,并将该棋盘状态的列表加入返回列表。

1.4 代码

/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function(n) {
  const paths = [];

  const backtracking = (row, path = []) => {
    if (row === n) {
      paths.push(path.map(pos => {
        const strArr = Array(n).fill('.');
        strArr[pos[1]] = 'Q';
        return strArr.join('');
      }));
      return;
    }

    for(let i = 0; i < n; i++) {
      let isAttact = false;
      for(const pos of path) {
        if (i === pos[1] || row + i === pos[0] + pos[1] || row - i === pos[0] - pos[1]) {
          isAttact = true;
          break;
        }
      }
      if (isAttact) continue;
      path.push([row, i]);
      backtracking(row + 1, path);
      path.pop();
    }
  }

  backtracking(0);

  return paths;
};

2. N皇后 II

2.1 问题描述

n 皇后问题 研究的是如何将 n 个皇后放置在 n × n 的棋盘上,并且使皇后彼此之间不能相互攻击。

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

2.2 要求

示例 1:

img

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

示例 2:

输入:n = 1
输出:1

2.3 思路

这道题和 N 皇后 非常相似,区别在于, N 皇后 需要得到所有可能的解,这道题只需要得到可能的解的数量。因此这道题可以使用 N 皇后 的做法,只需要将得到所有可能的解改成得到可能的解的数量即可。

2.4 代码

/**
 * @param {number} n
 * @return {number}
 */
var totalNQueens = function(n) {
  const paths = [];
  let result = 0;

  const backtracking = (row, path = []) => {
    if (row === n) {
      result ++;
      return;
    }

    for(let i = 0; i < n; i++) {
      let isAttact = false;
      for(const pos of path) {
        if (i === pos[1] || row + i === pos[0] + pos[1] || row - i === pos[0] - pos[1]) {
          isAttact = true;
          break;
        }
      }
      if (isAttact) continue;
      path.push([row, i]);
      backtracking(row + 1, path);
      path.pop();
    }
  }

  backtracking(0);

  return result;
};