【LeetCode从零单刷】N-Queens II

题目:

Follow up for N-Queens problem.

Now, instead outputting board configurations, return the total number of distinct solutions.

解答:

思路很简单,就是暴力求解的N皇后问题计数。过程如下:

  1. 如果第 i 行的第 j 列放着皇后,然后放第 (i+1) 行的皇后使其不矛盾,然后第 (i+2) 行……;
  2. 如果每一列都不可行,那我们就回溯一行,然后继续第 1 步直至成功(转3)或者失败。失败了再次回溯一行然后转第 1 步;
  3. 成功在第 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);
	}
};

对于非递归版本,比这个就麻烦多了。可以查看 传送门(应该是正确的解法,但是我写了一遍却是超时不知道为什么……)

最重要的,以上连接中介绍了一种位运算的方法(反正我没看懂,毕竟菜鸡),是最快的求解方法。

你可能感兴趣的:(LeetCode,C++,递归,回溯,N皇后)