图解N皇后:用"象棋战术"巧解经典难题!
大家好,我是忍者算法。今天要挑战的是一道算法界的"皇后级"难题 - LeetCode 51「N皇后」。这道题困扰了无数程序员,但今天我们用象棋玩家的思维,一步步将它拆解成简单易懂的小问题!
🎮 从象棋到算法
你有没有下过国际象棋?皇后是棋盘上最强大的棋子,她可以横向、纵向、斜向任意方向移动任意步数。N皇后问题就像是在玩一局特殊的象棋:我们需要在N×N的棋盘上放置N个皇后,让她们互相都无法攻击到对方。
想象你在玩一局特殊的象棋,需要一步步地把皇后放在安全的位置上。每放置一个皇后,就会在棋盘上形成一片"危险区域",下一个皇后就不能放在这些位置上。这不就是我们程序要解决的问题吗?
💡 问题本质剖析
题目要求:
在一个N×N的棋盘上放置N个皇后,使得任意两个皇后都不能互相攻击。返回所有可能的解决方案。
输入输出示例:
输入:n = 4
输出:[
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
🎯 思路突破
就像下象棋时,我们会思考:
- 每一行必须有一个皇后(因为如果同一行有两个皇后必然会互相攻击)
- 每一列必须有一个皇后(同理)
- 任意两个皇后不能在同一条斜线上
这启发我们可以:
- 逐行放置皇后
- 记录已经被攻击的列和斜线
- 用回溯法尝试所有可能的放置方案
🚀 代码实现
class Solution {
public List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<>();
// 用一维数组queens记录每行皇后的列位置
int[] queens = new int[n];
// 三个布尔数组分别记录:列、主对角线、副对角线的占用情况
boolean[] cols = new boolean[n];
boolean[] diag1 = new boolean[2*n-1]; // 主对角线 r+c
boolean[] diag2 = new boolean[2*n-1]; // 副对角线 r-c+n-1
// 从第0行开始放置皇后
backtrack(n, 0, queens, cols, diag1, diag2, result);
return result;
}
private void backtrack(int n, int row, int[] queens, boolean[] cols,
boolean[] diag1, boolean[] diag2, List<List<String>> result) {
// 成功放置完所有皇后
if (row == n) {
result.add(generateBoard(queens, n));
return;
}
// 尝试在当前行的每一列放置皇后
for (int col = 0; col < n; col++) {
// 计算当前位置的对角线索引
int d1 = row + col;
int d2 = row - col + n - 1;
// 如果当前位置不被攻击
if (!cols[col] && !diag1[d1] && !diag2[d2]) {
// 放置皇后
queens[row] = col;
cols[col] = true;
diag1[d1] = true;
diag2[d2] = true;
// 递归处理下一行
backtrack(n, row + 1, queens, cols, diag1, diag2, result);
// 回溯:撤销当前位置的皇后
cols[col] = false;
diag1[d1] = false;
diag2[d2] = false;
}
}
}
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;
}
}
📝 解题思路详解
让我们像下象棋一样,一步步理解这个解法:
1. 棋盘表示
我们用一维数组 queens 记录每行皇后的列位置,这比二维数组更高效。比如 queens[2] = 3 表示第2行的皇后放在第3列。
2. 攻击区域检查
为了快速判断一个位置是否安全,我们用三个布尔数组记录被攻击的位置:
- cols[j] 表示第j列是否有皇后
- diag1[i+j] 表示主对角线是否有皇后
- diag2[i-j+n-1] 表示副对角线是否有皇后
这就像象棋中提前计算好皇后的攻击范围!
3. 回溯过程
就像下象棋时的思考过程:
- 在当前行找一个安全的位置放皇后
- 标记这个皇后的攻击范围
- 转移到下一行继续放置
- 如果遇到死路,就回溯到上一步重新尝试
🔍 优化技巧
-
判断优化:
- 使用位运算代替布尔数组,可以进一步优化空间和时间
- 预先计算每个位置的攻击范围
-
空间优化:
- 只用一维数组记录皇后位置
- 用整数代替布尔数组记录攻击情况
-
回溯优化:
- 可以利用对称性减少搜索范围
- 首行皇后只需要搜索一半位置
🎯 相关题目推荐
- N皇后 II(LeetCode 52)- 只需要计算解的数量
- 解数独(LeetCode 37)- 类似的回溯思想
- 放置盒子(LeetCode 1411)- 类似的约束放置问题
🌟 面试常见追问
-
如何处理大规模问题?
- 可以使用并行计算
- 利用问题的对称性减少计算量
-
能否用其他算法解决?
- 可以用约束编程(Constraint Programming)
- 可以用遗传算法等启发式方法
作者:忍者算法
公众号:忍者算法
🎁 回复【刷题清单】获取LeetCode高频面试题合集
🧑💻 回复【代码】获取多语言完整题解
💡 回复【加群】加入算法交流群,一起进步
#算法面试 #LeetCode #回溯算法 #经典算法