LeetCode 52:N皇后 II

LeetCode 52:N皇后 II

在这里插入图片描述

问题本质与核心目标

N皇后 II 要求计算在 N×N 棋盘上放置 N 个皇后的合法方案数(皇后间互不攻击,即同一行、列、对角线无重复)。与第51题(输出所有布局)不同,本题只需计数,因此可通过 回溯法 + 冲突标记优化 高效求解。

核心思路:回溯 + 冲突标记数组

1. 冲突判断的优化

皇后冲突分为三类:列冲突主对角线冲突副对角线冲突。通过三个布尔数组快速标记:

  • 列冲突cols[col] → 标记列 col 是否已有皇后。
  • 主对角线冲突:主对角线上的点满足 row - col = 常数,转换为非负索引 row - col + (n-1)(避免负数),用 diag1[] 标记。
  • 副对角线冲突:副对角线上的点满足 row + col = 常数,用 diag2[] 标记。
2. 回溯流程
  • 逐行放置:从第0行开始,尝试当前行的每一列。
  • 冲突检查:利用三个标记数组,O(1) 时间判断当前列是否可放置皇后。
  • 递归与回溯:若可放置,标记冲突并递归处理下一行;递归返回后,回溯(撤销标记)以尝试其他列。
  • 计数触发:当处理完所有行(row == n),说明找到一种合法方案,计数加一。

算法步骤详解

步骤 1:初始化标记数组与计数
class Solution {
    private int count = 0; // 合法方案数(类成员,方便递归修改)
    
    public int totalNQueens(int n) {
        boolean[] cols = new boolean[n]; // 列冲突标记
        boolean[] diag1 = new boolean[2 * n - 1]; // 主对角线冲突标记
        boolean[] diag2 = new boolean[2 * n - 1]; // 副对角线冲突标记
        
        backtrack(0, n, cols, diag1, diag2); // 从第0行开始回溯
        return count;
    }
}
步骤 2:回溯函数 backtrack
private void backtrack(int row, int n, boolean[] cols, boolean[] diag1, boolean[] diag2) {
    // 终止条件:所有行已放置皇后,找到一种合法方案
    if (row == n) {
        count++;
        return;
    }
    
    // 遍历当前行的所有列(0 ~ n-1)
    for (int col = 0; col < n; col++) {
        // 计算主、副对角线的索引(转换为非负)
        int d1 = row - col + n - 1; // 主对角线索引:避免负数,范围 [0, 2n-2]
        int d2 = row + col;         // 副对角线索引:范围 [0, 2n-2]
        
        // 检查列、主对角线、副对角线是否均未被占用
        if (!cols[col] && !diag1[d1] && !diag2[d2]) {
            // 放置皇后:标记冲突
            cols[col] = true;
            diag1[d1] = true;
            diag2[d2] = true;
            
            // 递归处理下一行
            backtrack(row + 1, n, cols, diag1, diag2);
            
            // 回溯:撤销标记,尝试下一个列
            cols[col] = false;
            diag1[d1] = false;
            diag2[d2] = false;
        }
    }
}

关键逻辑解析

1. 对角线索引的数学推导
  • 主对角线
    对于点 (row, col),主对角线的特征是 row - col 为定值。由于 rowcol 范围是 [0, n-1]row - col 的范围是 [-(n-1), n-1]。为避免负数索引,加上 n-1,得到 [0, 2n-2],对应数组 diag1 的长度 2n-1

  • 副对角线
    副对角线的特征是 row + col 为定值,范围是 [0, 2n-2],对应数组 diag2 的长度 2n-1

2. 回溯的效率优化
  • 冲突检查:通过三个布尔数组,将每次冲突检查的时间从 O(n)(遍历前面行)降为 O(1),大幅减少无效递归。
  • 空间复用:标记数组在递归过程中被反复修改(标记→回溯→标记),空间复杂度仅为 O(n)(远低于存储所有布局的方案)。
3. 递归流程示例(以 n=4 为例)
  1. 初始状态row=0colsdiag1diag2 全为 false
  2. 处理 row=0
    • 尝试 col=0
      • 计算 d1=0-0+3=3d2=0+0=0,三者均为 false → 标记为 true,递归到 row=1
    • row=1,尝试 col=2(避开已标记的列和对角线),标记后递归到 row=2
    • 最终当 row=4(等于 n=4)时,count 加1。
  3. 回溯:递归返回后,撤销标记,继续尝试当前行的下一个列,直到所有可能的列遍历完毕。

完整代码(Java)

class Solution {
    private int count = 0; // 记录合法方案的数量

    public int totalNQueens(int n) {
        // 初始化三个标记数组:列、主对角线、副对角线
        boolean[] cols = new boolean[n]; // 列是否被占用
        boolean[] diag1 = new boolean[2 * n - 1]; // 主对角线标记,索引为 row - col + n - 1
        boolean[] diag2 = new boolean[2 * n - 1]; // 副对角线标记,索引为 row + col

        // 从第0行开始回溯
        backtrack(0, n, cols, diag1, diag2);

        return count;
    }

    /**
     * 回溯函数:尝试在第row行放置皇后
     * @param row 当前处理的行
     * @param n 皇后数量(棋盘大小)
     * @param cols 列占用标记数组
     * @param diag1 主对角线占用标记数组
     * @param diag2 副对角线占用标记数组
     */
    private void backtrack(int row, int n, boolean[] cols, boolean[] diag1, boolean[] diag2) {
        // 终止条件:所有行都已成功放置皇后
        if (row == n) {
            count++; // 找到一种合法方案,计数加1
            return;
        }

        // 遍历当前行的每一列,尝试放置皇后
        for (int col = 0; col < n; col++) {
            // 计算当前位置对应的主、副对角线索引
            int d1 = row - col + n - 1; // 主对角线索引(避免负数,范围0~2n-2)
            int d2 = row + col; // 副对角线索引(范围0~2n-2)

            // 检查列、主对角线、副对角线是否都未被占用
            if (!cols[col] && !diag1[d1] && !diag2[d2]) {
                // 放置皇后:标记列和对角线为已占用
                cols[col] = true;
                diag1[d1] = true;
                diag2[d2] = true;

                // 递归处理下一行
                backtrack(row + 1, n, cols, diag1, diag2);

                // 回溯:撤销标记,尝试下一个列
                cols[col] = false;
                diag1[d1] = false;
                diag2[d2] = false;
            }
        }
    }
}

复杂度分析

  • 时间复杂度:最坏情况下接近 O(n!)(无剪枝时),但通过冲突标记的剪枝,实际远低于 O(n!)(例如 n=9 时仍可高效运行)。
  • 空间复杂度O(n),三个标记数组的长度分别为 n2n-12n-1(均为 O(n) 级别),递归栈深度为 n

该方法通过 回溯 + 冲突标记数组,将冲突检查优化到 O(1),在保证正确性的同时大幅提升效率,是解决N皇后计数问题的最优方案。核心思想可推广到类似的组合优化问题(如排列、棋盘覆盖等),体现了回溯算法与状态剪枝的强大结合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值