棋盘类问题一直是算法竞赛和编程面试中的经典题型,其中N皇后问题更是回溯算法的"教科书式"案例。在实际解题过程中,我发现这类问题虽然表面相似,但每个变种都有其独特的解题技巧和易错点。今天我就结合三个典型题目,分享回溯算法在棋盘问题中的实战应用。
给定一个n×n的棋盘,其中部分位置不可放置皇后(标记为0)。需要在棋盘上放置n个黑皇后和n个白皇后,满足:
这个问题可以看作是经典N皇后问题的扩展,需要同时处理两种棋子的约束条件。
采用回溯法(DFS)的解题框架:
cpp复制void dfs(int row, int color) {
if (row == n) {
if (color == 0) dfs(0, 1); // 黑皇后放完开始放白皇后
else res++; // 两种都放完,解+1
return;
}
for (int col = 0; col < n; col++) {
if (可放置(row, col, color)) {
放置皇后(row, col, color);
dfs(row + 1, color);
移除皇后(row, col, color);
}
}
}
使用三个数组分别记录列和两条对角线的占用情况:
cpp复制bool usedCol[2][20]; // 列占用 [颜色][列]
bool usedDiag1[2][40]; // 主对角线 [颜色][r-c+n]
bool usedDiag2[2][40]; // 副对角线 [颜色][r+c]
对角线索引计算:
r - c + n(加n避免负数)r + ccpp复制void setState(int r, int c, bool state, int color) {
usedCol[color][c] = state;
usedDiag1[color][r-c+n] = state;
usedDiag2[color][r+c] = state;
bd[r][c] = state ? 0 : 1; // 临时标记位置不可用
}
提示:调试时可以打印中间状态,观察每种颜色放置后的棋盘情况,确保没有非法覆盖。
在经典8皇后问题基础上,每个棋盘格有数字权重,要求找出使皇后所在位置数字之和最大的解。
基本框架不变,增加当前和的计算与比较:
cpp复制void dfs(int row, int currentSum) {
if (row == 8) {
maxSum = max(maxSum, currentSum);
return;
}
for (int col = 0; col < 8; col++) {
if (可放置(row, col)) {
放置皇后(row, col);
dfs(row + 1, currentSum + board[row][col]);
移除皇后(row, col);
}
}
}
在带洞棋盘上放置车(Rook),车的攻击范围会被洞阻断。需要计算放置k个车的方案数(k从0到最大可放数)。
冲突检测改进:
位置跳跃优化:
cpp复制void nextPos(int &r, int &c, bool jump) {
if (++c == n) { c = 0; r++; jump = false; }
if (jump && board[r][c] == 1) nextPos(r, c, true);
}
棋盘表示:
冲突检查函数:
cpp复制bool canPlace(int r, int c) {
if (board[r][c] != 1) return false;
for (int i = r-1; i >= 0 && board[i][c] != 0; i--)
if (board[i][c] == 2) return false;
for (int i = r+1; i < n && board[i][c] != 0; i++)
if (board[i][c] == 2) return false;
return true;
}
对于n×n棋盘:
在解决棋盘多项式问题时,我最初忽略了行内被洞分隔的区域可以独立放置车的特性,导致搜索不完整。后来通过引入jump机制,才正确覆盖了所有可能情况。这提醒我们,对问题约束条件的理解必须非常精确。
回溯算法通用框架:
cpp复制void backtrack(状态) {
if (终止条件) {
记录结果;
return;
}
for (选择 : 所有可选选项) {
if (约束满足) {
做选择;
backtrack(新状态);
撤销选择;
}
}
}
针对棋盘问题的特化版本:
cpp复制void dfs(int row) {
if (row == n) { 计数++; return; }
for (int col = 0; col < n; col++) {
if (!冲突(row, col)) {
放置棋子(row, col);
dfs(row + 1);
移除棋子(row, col);
}
}
}
通过系统性地练习这些变种问题,我对回溯算法的理解更加深入。在实际编码时,建议先从最基础的N皇后问题入手,掌握核心框架后,再逐步挑战更复杂的变种。记住,清晰的思路比盲目的编码更重要,画图分析往往能事半功倍。