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

【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.dtcms.com/a/301678.html

相关文章:

  • WiFi Mouse PC端 v1.7.2 官方中文版
  • GIF图像格式
  • 【RAG技术权威指南】从原理到企业级应用实践
  • Git Commit 生成与合入 Patch 指南
  • 《关于matplot中绘制图像中文字体乱码问题》
  • AWS免费套餐全面升级:企业降本增效与技术创新解决方案
  • 物联网发展:从概念到应用的演变历程
  • vue3报错:this.$refs.** undefined
  • 【INT范围提取字符串数字为正数】2022-8-29
  • Linux文件系统(三)
  • Java常用日志框架介绍
  • 【笔记】菲克定律与连续性方程详述
  • 【测试报告】博客系统(Java+Selenium+Jmeter自动化测试)
  • 【Milvus合集】1.Milvus 的核心概念(collection、field、index、partition、segment)
  • 【lucene】向量搜索底层文件关系梳理
  • springboot实现打印每个接口请求的出参和入参
  • SpringBoot配置文件详解
  • jangow-01-1.0.1靶机
  • Java 后端 Cookie Session Token会话跟踪技术
  • Maven之多模块项目管理
  • c# everthing.exe 通信
  • Level MC-9“恶地”
  • 1.qt历史版本安装与多版本开发(解决被拦截问题)
  • 青少年编程能力等级测评试卷及答案 Python编程(三级)
  • 《频率之光:共生之恋》
  • C9300L Stacking堆叠
  • 人形机器人指南(十)决策
  • 快速了解线性回归算法
  • Item16:成对使用new和delete时要采取相同形式
  • Sklearn 机器学习 数值指标 混淆矩阵confusion matrix