当前位置: 首页 > java >正文

【LeetCode 热题 100】51. N 皇后——回溯

Problem: 51. N 皇后
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N!)
    • 空间复杂度:O(N)

整体思路

这段代码旨在解决经典的组合搜索问题:N皇后问题 (N-Queens)。问题要求在一个 N×N 的棋盘上放置 N 个皇后,使得它们互相之间不能攻击。这意味着任意两个皇后都不能处于同一行、同一列或同一条对角线上。代码需要找出所有可能的解决方案。

该算法采用的核心方法是 回溯法(Backtracking),通过 深度优先搜索(DFS) 来系统地探索所有可能的皇后布局。为了高效地判断位置是否安全,算法使用了几个布尔数组来记录列和对角线的占用情况。

算法的整体思路可以分解为以下步骤:

  1. 逐行放置皇后

    • 算法将放置 N 个皇后的过程,看作是依次在第 0 行、第 1 行、…、第 n-1 行分别放置一个皇后。
    • 它定义了一个 dfs(r, ...) 函数,其核心任务是在第 r 行找到一个安全的位置来放置皇后。由于我们是逐行放置的,所以自然满足了“不同行”的约束。
  2. 高效的位置合法性判断

    • 为了快速判断在 (r, c) 位置放皇后是否安全,算法需要检查该位置所在的两条对角线是否已被占用。它使用了三个布尔数组:
      • boolean[] col: 大小为 ncol[c] = true 表示第 c 列已被占用。
      • boolean[] diag1: 大小为 2n-1。用于标记主对角线(从左上到右下)。同一条主对角线上的所有格子 (r, c),其 r + c 的值是恒定的。我们用 r + c 作为 diag1 的索引。
      • boolean[] diag2: 大小为 2n-1。用于标记副对角线(从右上到左下)。同一条副对角线上的所有格子 (r, c),其 r - c 的值是恒定的。为了避免负数索引,通常会加上一个偏移量 n-1,所以用 r - c + n - 1 作为 diag2 的索引。
  3. 递归与回溯的核心逻辑

    • dfs(r, ...) 函数尝试在第 r 行的每一列 c (从 0到 n-1) 放置皇后。
    • 检查合法性:对于每个 (r, c),通过检查 !col[c] && !diag1[r+c] && !diag2[r-c+n-1] 来判断该位置是否安全。
    • 选择 (Choose):如果位置安全,就做出选择:
      a. 将 (r, c) 的列和两条对角线标记为已占用。
      b. 记录皇后位置:queens[r] = c,表示第 r 行的皇后放在了第 c 列。
    • 探索 (Explore):递归调用 dfs(r + 1, ...),去解决下一行 r+1 的放置问题。
    • 撤销选择 (Unchoose / Backtrack):当 dfs(r + 1, ...) 返回后,必须撤销刚才的选择。将 (r, c) 的列和两条对角线的标记恢复为 false。这是回溯法的精髓,它使得在后续的循环中,可以尝试在第 r 行的其他列 c 放置皇后。
  4. 递归终止与方案构建

    • r 的值等于 n 时,意味着从第 0 行到第 n-1 行都成功地放置了一个皇后。
    • 此时,一个完整的解决方案就找到了。queens 数组中存储了该方案的所有皇后位置。
    • 代码会根据 queens 数组来构建一个 List<String> 形式的棋盘表示,并将其加入到最终的结果列表 ans 中。

完整代码

class Solution {/*** 解决 N 皇后问题,返回所有不同的解决方案。* @param n 棋盘大小和皇后数量* @return 包含所有解决方案的列表,每个方案是一个棋盘的字符串表示*/public List<List<String>> solveNQueens(int n) {// ans: 最终结果列表,存储所有解决方案List<List<String>> ans = new ArrayList<>();// queens[r] = c 表示第 r 行的皇后放在了第 c 列int[] queens = new int[n];// col[c] = true 表示第 c 列被占用boolean[] col = new boolean[n];// diag1[r+c] = true 表示主对角线 r+c 被占用boolean[] diag1 = new boolean[2 * n - 1]; // 索引范围是 0 到 2n-2// diag2[r-c+n-1] = true 表示副对角线 r-c+n-1 被占用boolean[] diag2 = new boolean[2 * n - 1]; // 索引范围是 0 到 2n-2// 从第 0 行开始进行深度优先搜索dfs(0, queens, ans, col, diag1, diag2);return ans;}/*** 深度优先搜索(回溯)辅助函数。* @param r 当前准备放置皇后的行号* @param queens 记录每行皇后位置的数组* @param ans 结果列表* @param col 列占用标记数组* @param diag1 主对角线占用标记数组* @param diag2 副对角线占用标记数组*/private void dfs(int r, int[] queens, List<List<String>> ans, boolean[] col, boolean[] diag1, boolean[] diag2) {int n = col.length;// 递归终止条件:当 r == n 时,说明所有 n 行都已成功放置皇后if (r == n) {// 构建棋盘的字符串表示List<String> board = new ArrayList<>(n);for (int c : queens) {char[] row = new char[n];Arrays.fill(row, '.');row[c] = 'Q';board.add(new String(row));}// 将构建好的解决方案加入结果列表ans.add(board);return;}// 尝试在当前行 r 的每一列 c 放置皇后for (int c = 0; c < n; c++) {// 计算两条对角线的索引int d1_idx = r + c;int d2_idx = r - c + n - 1;// 检查当前位置 (r, c) 是否安全if (!col[c] && !diag1[d1_idx] && !diag2[d2_idx]) {// 选择 (Choose): 在 (r, c) 放置皇后col[c] = diag1[d1_idx] = diag2[d2_idx] = true;queens[r] = c;// 探索 (Explore): 递归地去处理下一行dfs(r + 1, queens, ans, col, diag1, diag2);// 撤销选择 (Unchoose / Backtrack): 恢复状态,将皇后从 (r, c) 移走col[c] = diag1[d1_idx] = diag2[d2_idx] = false;// queens[r] 的值会被下一轮循环覆盖,无需显式重置}}}
}

时空复杂度

时间复杂度:O(N!)

  1. 搜索空间:这是一个典型的全排列问题的变种。在第 0 行,我们有 N 个选择;在第 1 行,最多有 N-1 个选择(因为列不能重复),以此类推。这给出了一个 N * (N-1) * ... * 1 = N! 的上界。
  2. 剪枝:由于对角线的约束,实际的搜索空间会比 N! 小很多,但其增长趋势仍然是阶乘级别的。没有一个简单的封闭形式来精确描述N皇后问题的解的数量或搜索树的节点数,但 O(N!) 是一个被广泛接受的、描述其计算复杂度的上界。
  3. 方案构建:当找到一个解决方案时,需要花费 O(N^2) 的时间来构建棋盘的字符串表示。设 S(N) 为解的数量,这部分的总时间是 S(N) * O(N^2)
  4. 综合分析
    • 搜索过程的时间复杂度远大于方案构建的总时间。
    • 因此,整个算法的时间复杂度由搜索树的节点数决定,可以粗略地用 O(N!) 来表示。

空间复杂度:O(N)

  1. 主要存储开销:我们分析的是额外辅助空间,不包括存储最终结果的 ans 列表。
    • int[] queens: 大小为 N
    • boolean[] col: 大小为 N
    • boolean[] diag1: 大小为 2N-1
    • boolean[] diag2: 大小为 2N-1
    • 递归调用栈dfs 函数的最大递归深度为 N
  2. 综合分析
    • 所有辅助数组和递归栈的深度都与 N 成线性关系。
    • O(N) + O(N) + O(2N-1) + O(2N-1) + O(N) = O(N)。
    • 因此,总的辅助空间复杂度为 O(N)

参考灵神

http://www.xdnf.cn/news/16508.html

相关文章:

  • Java面试全方位解析:从基础到AI的技术交锋
  • 【Python系列】使用 memory_profiler 诊断 Flask 应用内存问题
  • 单表查询-or优化
  • K-近邻算法
  • Linux之shell脚本篇(三)
  • 3D碰撞检测系统 基于SAT算法+Burst优化(Unity)
  • rust- 定义模块以控制作用域和隐私
  • 任务提醒工具怎么选?对比16款热门软件
  • 2025年Agent创业实战指南:从0到1打造高增长AI智能体项目
  • 撤销连续三年不使用注册商标一次下受理书!
  • Spring之【Bean的生命周期】
  • Android MQTT 长连接最佳实践技术分享
  • Amazon Relational Database Service (Amazon RDS)入门课
  • C++ 构造函数中阻止资源泄漏的实践探索
  • Linux驱动20 --- FFMPEG视频API
  • 【 Python 】Collections库权威指南
  • 【多模态】天池AFAC赛道四-智能体赋能的金融多模态报告自动化生成part1-数据获取
  • 卫星图像数据集在农业领域的应用
  • Leetcode力扣解题记录--第136题(查找单数)
  • Redis C++客户端——命令使用
  • Vue 框架 学习笔记
  • 9-大语言模型—Transformer 核心:多头注意力的 10 步拆解与可视化理解
  • 【在Unity游戏开发中Dictionary、List介绍】
  • MongoDB索引及其原理
  • 2025 DevOps开源工具全景指南:构建面向未来的智能交付体系
  • 代码随想录训练因第三十天| 39.组合总和 40.组合总和ll 131.分割回文串
  • PyTorch武侠演义 第一卷:初入江湖 第7章:矿洞中的计算禁制
  • 链表算法综合——重排链表
  • 望言OCR视频字幕提取2025终极评测:免费版VS专业版提全方位对比(含免费下载)
  • 重生之我在暑假学习微服务第二天《MybatisPlus-下篇》