深搜练习(N皇后)(10)

一.题目

51. N 皇后 - 力扣(LeetCode)

二.思路讲解

2.1 思路讲解

N皇后问题 是回溯算法的经典应用。我们采用逐行放置 的策略:每一行只能放一个皇后,因此行冲突自然避免。接下来只需要确保放置的皇后不与之前任何皇后在同一列或同一对角线即可。为此,我们需要维护三个布尔数组(哈希表)来记录被占用的列、主对角线和副对角线。

  • 列判断 :用一个数组 checkCol 记录哪些列已被占用。

  • 主对角线 :在棋盘上,同一主对角线上的格子满足 行号 - 列号 为常数。由于这个差值可能为负数,我们统一加上一个偏移量(例如 n),映射到非负下标,用数组 checkDig1 记录。

  • 副对角线 :同一副对角线上的格子满足 行号 + 列号 为常数,该值范围在 [0, 2n-2],直接用数组 checkDig2 记录。

递归过程 :从第 0 行开始,依次尝试每一列。如果当前位置的三个方向都未被占用,则放置皇后,并标记三个方向;然后递归处理下一行;递归返回后,撤销标记(回溯),继续尝试下一列。当成功放置完所有行时,将当前棋盘状态加入结果集。

剪枝 :在尝试每一列时,若发现列或对角线已被占用,则直接跳过,不进入递归。这种提前判断避免了无效的放置,是回溯剪枝的典型应用。

三.代码演示

cpp 复制代码
class Solution {
public:
    bool checkCol[10],checkDig1[20],checkDig2[20];//x轴,主对角线,副对角线
    vector<vector<string>>ret;
    vector<string>path;
    vector<vector<string>> solveNQueens(int n) 
    {
        path.resize(n);
        for(int i = 0;i < n;i++)
        {
            path[i].append(n,'.');//初始化棋盘
        }
        dfs(n,0);
        return ret;
    }
    void dfs(int n,int row)
    {
        if(row == n)
        {
            ret.push_back(path);
            return;
        }
        for(int col = 0;col < n;col++)
        {
            //剪枝判断x轴、主对角线、副对角线是否合法
            if(checkCol[col] != true && checkDig1[row - col + n] != true && checkDig2[row + col] != true)
            {
                path[row][col] = 'Q';
                checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = true;//表示使用
                dfs(n,row + 1);
                //回溯
                path[row][col] = '.';
                checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = false;//表示空闲
            }
        }
    }
};

四.代码讲解

一、全局变量设计

为了实现回溯并记录棋盘状态,我们定义以下成员变量

  • checkCol[10]:布尔数组,记录每一列是否已被占用。由于 n ≤ 9,大小 10 足够。

  • checkDig1[20] :布尔数组,记录每条主对角线是否已被占用。主对角线上的格子满足 row - col 为常数,范围 [-(n-1), n-1],加上偏移量 n 后映射到 [1, 2n-1],大小 20 足够。

  • checkDig2[20] :布尔数组,记录每条副对角线是否已被占用。副对角线上的格子满足 row + col 为常数,范围 [0, 2n-2],大小 20 足够。

  • ret:二维字符串数组,存储所有解,每个解是一个字符串向量(棋盘)。

  • path:一维字符串数组,表示当前正在构建的棋盘(每一行是一个字符串)。

二、主函数 solveNQueens

主函数接收整数 n,首先将 path 大小调整为 n,并用 '.' 初始化每一行的所有列,形成一个 n × n 的棋盘。然后调用递归函数 dfs(n, 0) 从第 0 行开始放置皇后,最后返回结果集 ret

三、递归函数 dfs

递归函数 dfs(int n, int row) 的含义是:已经在前 row 行成功放置了皇后,接下来要在第 row 行放置皇后。执行流程如下:

1. 递归终止条件

row == n 时,说明所有 n 行都已成功放置皇后,得到一个完整解,将当前棋盘 path 的副本加入 ret,然后返回。

2. 遍历列并尝试放置

使用 for 循环遍历当前行的每一列 col(0 到 n-1)。对于每一列,检查当前位置是否与已放置的皇后冲突:

  • 列冲突checkCol[col] == false

  • 主对角线冲突checkDig1[row - col + n] == false(加 n 防止负数下标)

  • 副对角线冲突checkDig2[row + col] == false

如果三个条件都满足(即均为 false),则可以在 (row, col) 放置皇后:

  • 将棋盘 path[row][col] 设为 'Q'

  • 将三个方向标记为已占用:checkCol[col] = truecheckDig1[row - col + n] = truecheckDig2[row + col] = true

  • 递归调用 dfs(n, row + 1),处理下一行。

  • 递归返回后,执行回溯

    • path[row][col] 恢复为 '.'

    • 将三个方向的标记恢复为 false,以便尝试其他列。

四、关键细节
  • 数组大小 :n ≤ 9 时,row - col 最小为 -(n-1),加 n 后最小为 1;最大为 (n-1) + n = 2n-1 ≤ 17row + col 最大为 2n-2 ≤ 16。因此 checkDig1checkDig2 大小 20 足够。

  • 下标偏移 :主对角线索引通过 row - col + n 映射到非负范围,避免了负数下标。

相关推荐
Controller-Inversion2 小时前
322. 零钱兑换
算法
头发够用的程序员2 小时前
C++和Python面试经典算法汇总(一)
开发语言·c++·python·算法·容器·面试
淡海水3 小时前
【AI模型】模型量化技术详解
人工智能·算法·机器学习
炸膛坦客3 小时前
嵌入式 - 数据结构与算法:(1-1)数据结构 - 顺序表(Sequential List)
数据结构·算法·嵌入式
水龙吟啸3 小时前
数据结构与算法随机复习–Day1
数据结构·c++·算法
生成论实验室3 小时前
《事件关系阴阳博弈动力学:识势应势之道》第八篇:认知与反思关系——探索、定位与延续
人工智能·算法·架构·知识图谱·创业创新
十五年专注C++开发3 小时前
浅谈LLVM
开发语言·c++·qt·clang·llvm
YaraMemo3 小时前
一文带你区分全局最优解和帕累托最优解
算法·5g·信息与通信·信号处理
白夜11174 小时前
C++(标签派发 Tag Dispatching)
开发语言·c++·笔记·算法