深搜练习(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 映射到非负范围,避免了负数下标。

相关推荐
学习中.........11 分钟前
Karpathy nanoGPT 教程到底讲了什么
人工智能·算法·语言模型
城管不管26 分钟前
重生——第十次面试之开源中国一面挂
java·linux·开发语言·算法·面试·职场和发展·开源
三十岁老牛再出发1 小时前
08.18每日总结
c++·python·numpy·pandas
地平线开发者1 小时前
征程6|YOLOv5x 在 Horizon 征程6 上的端到端部署实践(下)
算法·自动驾驶
speop2 小时前
llm-algo-leetcode |Task01
算法·leetcode·职场和发展
艾为电子2 小时前
【应用方案】电视沉浸式音频升级: 电视音频 awinic“芯片 + 算法” 一体化解决方案
算法·音视频
余额瞒着我当琳2 小时前
C++--vector底层,leetcode118杨辉三角实战,对于传统reserve的坑点,迭代器失效
开发语言·c++
大熊背2 小时前
树莓派相机自动白平衡详解(二)
算法·白平衡·isppipeline
醉城夜风~2 小时前
(超详细)C++多态(Polymorphism)
开发语言·c++
Scabbards_2 小时前
面试Leetcode - 算法合集
算法·leetcode·面试