N皇后问题是计算机科学和数学领域的经典问题,要求在一个N×N的棋盘上放置N个皇后,使得它们互不攻击。这个问题最早由国际象棋玩家马克斯·贝瑟尔在1848年提出,后来成为算法学习中回溯法的典型案例。
提示:N皇后问题看似简单,但随着N的增大,解的数量会呈指数级增长。例如,当N=8时(标准八皇后问题),共有92种不同的解法。
N皇后问题的核心约束条件包括:
这些约束条件将问题转化为一个典型的约束满足问题(CSP),我们需要找到所有满足这些约束条件的皇后放置方案。
最直观的解法是使用回溯算法:
java复制class Solution {
public List<List<String>> solveNQueens(int n) {
List<List<String>> solutions = new ArrayList<>();
int[] queens = new int[n]; // 记录每行皇后所在的列
Set<Integer> columns = new HashSet<>(); // 已占用的列
Set<Integer> diagonals1 = new HashSet<>(); // 主对角线(row-col)
Set<Integer> diagonals2 = new HashSet<>(); // 副对角线(row+col)
backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);
return solutions;
}
private void backtrack(List<List<String>> solutions, int[] queens, int n,
int row, Set<Integer> columns,
Set<Integer> diagonals1, Set<Integer> diagonals2) {
if (row == n) {
solutions.add(generateBoard(queens, n));
return;
}
for (int col = 0; col < n; col++) {
if (columns.contains(col)) continue;
int diagonal1 = row - col;
if (diagonals1.contains(diagonal1)) continue;
int diagonal2 = row + col;
if (diagonals2.contains(diagonal2)) continue;
// 放置皇后
queens[row] = col;
columns.add(col);
diagonals1.add(diagonal1);
diagonals2.add(diagonal2);
// 递归处理下一行
backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);
// 回溯
queens[row] = -1;
columns.remove(col);
diagonals1.remove(diagonal1);
diagonals2.remove(diagonal2);
}
}
private List<String> generateBoard(int[] queens, int n) {
List<String> board = new ArrayList<>();
for (int i = 0; i < n; i++) {
char[] row = new char[n];
Arrays.fill(row, '.');
row[queens[i]] = 'Q';
board.add(new String(row));
}
return board;
}
}
java复制class Solution {
public List<List<String>> solveNQueens(int n) {
List<List<String>> solutions = new ArrayList<>();
int[] queens = new int[n];
solve(solutions, queens, n, 0, 0, 0, 0);
return solutions;
}
private void solve(List<List<String>> solutions, int[] queens, int n,
int row, int columns, int diagonals1, int diagonals2) {
if (row == n) {
solutions.add(generateBoard(queens, n));
return;
}
int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));
while (availablePositions != 0) {
int position = availablePositions & (-availablePositions);
availablePositions = availablePositions & (availablePositions - 1);
int column = Integer.bitCount(position - 1);
queens[row] = column;
solve(solutions, queens, n, row + 1,
columns | position,
(diagonals1 | position) << 1,
(diagonals2 | position) >> 1);
}
}
private List<String> generateBoard(int[] queens, int n) {
List<String> board = new ArrayList<>();
for (int i = 0; i < n; i++) {
char[] row = new char[n];
Arrays.fill(row, '.');
row[queens[i]] = 'Q';
board.add(new String(row));
}
return board;
}
}
N皇后问题的时间复杂度为O(N!),这是因为:
虽然实际运行中由于剪枝,有效搜索空间会小于N!,但最坏情况下时间复杂度仍为O(N!)。
对角线冲突检测基于以下数学观察:
通过这两个公式,我们可以用O(1)时间判断两个位置是否在同一对角线上。
位运算优化的核心思想是:
关键位运算技巧:
x & (-x):获取最低位的1x & (x-1):清除最低位的1Integer.bitCount(x):计算1的个数N皇后问题的解空间本质上是一个排列问题。对于N×N棋盘:
对于较大的N值(如N>20),传统回溯方法不再适用,可以考虑:
虽然N皇后问题本身是一个理论问题,但其解法思想在以下领域有广泛应用:
在实际编码和面试准备过程中,我发现以下几点特别重要:
N皇后问题虽然经典,但真正掌握它需要反复练习和思考。建议读者不仅要能写出代码,还要理解每个优化步骤背后的原理,这样才能在遇到类似问题时举一反三。