题目:
Follow up for N-Queens problem.
Now, instead outputting board configurations, return the total number of distinct solutions.
解答:
思路很简单,就是暴力求解的N皇后问题计数。过程如下:
- 如果第 i 行的第 j 列放着皇后,然后放第 (i+1) 行的皇后使其不矛盾,然后第 (i+2) 行……;
- 如果每一列都不可行,那我们就回溯一行,然后继续第 1 步直至成功(转3)或者失败。失败了再次回溯一行然后转第 1 步;
- 成功在第 N 行上也放好了皇后,算一种解法。解法总数 +1,回溯到上一行,列数 +1,再次转到第 1 步。
思路也不难。就是代码的实现比较复杂。尤其是回溯的过程。但是如果利用递归的思想,就很简单了。如果在第 i 行的第 j 列放着皇后时的解法数目,表示为 S(i, j)。
解法总数 = S(0, 0) + S(0, 1) + ... + S(0, n)
= [S(1, 0, 此时 (0, 0) 处有一皇后) + S(1, 1, 此时 (0, 0) 处有一皇后) + ...] + [S(1, 0, 此时 (0, 1) 处有一皇后) + S(1, 1, 此时 (0, 1) 处有一皇后) + ...] + ...
= ......
等于就是画出了一棵庞大的树,统计每个子叶节点的解法个数汇集到父节点,父节点再汇集到父节点的父节点......这样一来根节点就是总的解法个数,而中途死掉的节点,计数时就不会计入。
此时如果利用二维数组来计数,执行起来会非常麻烦。这里介绍一个小技巧,仅仅利用一个一维数组,数组第 i 个元素 j 代表在棋盘第 i 行的第 j 列放置一个皇后(或者棋盘第 j 行的第 i 列,这个无所谓)。
同样,我们需要一个check函数判断当前放置一个皇后会不会与之前放置皇后产生矛盾。行、列的判断很简单,对角线的判断稍微复杂点,需要利用到一个性质:
对于坐标为 (a, b) 和 (c, d) 的两个点,如果在对角线相交会有 abs(a-c) == abs(b-d) 的性质。
最终代码:
class Solution { public: // 判断是否与之前已经填入的皇后产生冲突 bool check(int* queen, int count) { for (int i = 0; i < count; ++i) { if (queen[i] == queen[count] || abs(count - i) == abs(queen[count] - queen[i])) { return false; } } return true; } // 递归函数 int iterQueens(int* queen, int count, int row) { // row 最大有效值是 (n-1),如果 == n 说明已经填完棋盘 if (count == row) return 1; int sum = 0; for (int col = 0; col < count; ++col) { queen[row] = col; if (check(queen, row)) { sum = sum + iterQueens(queen, count, row + 1); } } return sum; } int totalNQueens(int n) { int* queen = new int[n]; // initiate for (int i = 0; i < n; ++i) { queen[i] = -1; } return iterQueens(queen, n, 0); } };
对于非递归版本,比这个就麻烦多了。可以查看传送门(应该是正确的解法,但是我写了一遍却是超时不知道为什么……)
最重要的,以上连接中介绍了一种位运算的方法(反正我没看懂,毕竟菜鸡),是最快的求解方法。