(leetcode)力扣100 62N皇后问题 (普通回溯(使用set存储),位运算回溯)

题目

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

数据范围

1 <= n <= 9

测试用例

示例1

java 复制代码
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例2

输入:n = 1

输出:[["Q"]]

题解1(不用看,博主的垃圾代码,虽然过了,但是性能最差,这里只是博主个人记录)

java 复制代码
class Solution {
    List<List<String>> res=new ArrayList<>();
    List<String> ans=new ArrayList<>();
    char[][] c;
    public List<List<String>> solveNQueens(int n) {
        c=new char[n][n];
        for(int i=0;i<n;i++){
            Arrays.fill(c[i],'.');
        }
        dfs(n,0);
        return res;
    }

    public void dfs(int n,int curr){
        if(curr==n){
            res.add(new ArrayList<>(ans));
        }

        for(int i=0;i<n;i++){
            if(isOK(curr,i,c,n,curr)){
                c[curr][i]='Q';
                ans.add(String.valueOf(c[curr]));
                dfs(n,curr+1);
                c[curr][i]='.';
                ans.remove(ans.size()-1);
            }
        }

    }

    public boolean isOK(int x,int y,char[][] c,int n,int curr){
        for(int i=0;i<curr;i++){
            for(int j=0;j<n;j++){
                if(c[i][j]=='Q'){
                    if(i==x||j==y||Math.abs(x-i)==Math.abs(y-j)){
                        return false;
                    }
                }
            }
        }

        return true;
    }

    
}

题解2(官解1,时间 ON!,空间ON)

java 复制代码
class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> solutions = new ArrayList<List<String>>();
        int[] queens = new int[n];
        Arrays.fill(queens, -1);
        Set<Integer> columns = new HashSet<Integer>();
        Set<Integer> diagonals1 = new HashSet<Integer>();
        Set<Integer> diagonals2 = new HashSet<Integer>();
        backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);
        return solutions;
    }

    public void backtrack(List<List<String>> solutions, int[] queens, int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
        if (row == n) {
            List<String> board = generateBoard(queens, n);
            solutions.add(board);
        } else {
            for (int i = 0; i < n; i++) {
                if (columns.contains(i)) {
                    continue;
                }
                int diagonal1 = row - i;
                if (diagonals1.contains(diagonal1)) {
                    continue;
                }
                int diagonal2 = row + i;
                if (diagonals2.contains(diagonal2)) {
                    continue;
                }
                queens[row] = i;
                columns.add(i);
                diagonals1.add(diagonal1);
                diagonals2.add(diagonal2);
                backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);
                queens[row] = -1;
                columns.remove(i);
                diagonals1.remove(diagonal1);
                diagonals2.remove(diagonal2);
            }
        }
    }

    public List<String> generateBoard(int[] queens, int n) {
        List<String> board = new ArrayList<String>();
        for (int i = 0; i < n; i++) {
            char[] row = new char[n];
            Arrays.fill(row, '.');
            row[queens[i]] = 'Q';
            board.add(new String(row));
        }
        return board;
    }
}

题解3(官解2,时空同上,但实际空间更好,因为没有试用set)

java 复制代码
import java.util.*;

class Solution {
    // 存放最终所有解的容器
    List<List<String>> res = new ArrayList<>();

    public List<List<String>> solveNQueens(int n) {
        // queen数组用于记录每一行皇后放到了哪一列
        // 下标是行(row),值是列(col)。例如 queen[0] = 2 表示第0行第2列有皇后
        int[] queen = new int[n];
        Arrays.fill(queen, -1);
        
        // 初始入口:第0行,三个攻击限制变量(列、左对角、右对角)全为0(无攻击)
        dfs(queen, n, 0, 0, 0, 0);
        return res;
    }

    /**
     * @param queen   用于生成结果的数组
     * @param n       棋盘大小
     * @param row     当前正在处理的行号
     * @param columns 列限制(1代表被上面行的皇后攻击了)
     * @param dial    左对角线限制(1代表被攻击),进入下一行时需要左移
     * @param diar    右对角线限制(1代表被攻击),进入下一行时需要右移
     */
    public void dfs(int[] queen, int n, int row, int columns, int dial, int diar) {
        // --- 递归终止条件 ---
        // 如果 row 等于 n,说明 0 ~ n-1 行都成功放好了皇后
        if (row == n) {
            res.add(construct(queen, n)); // 记录答案
            return; // 回溯:返回上一层,继续寻找其他可能性
        }

        /* * --- 核心逻辑:计算当前行所有能放皇后的位置 ---
         * 1. (columns | dial | diar): 
         * 把三种攻击范围并起来。结果中 1 代表"危险",0 代表"安全"。
         * * 2. ~(...): 
         * 取反。变成 1 代表"安全",0 代表"危险"。
         * (注意:取反后,高位会有很多无意义的 1,比如前 28 位都是 1)
         * * 3. ((1 << n) - 1): 
         * 生成掩码。比如 n=4,就是 0000...1111。
         * * 4. & 运算: 
         * 利用掩码把高位的乱七八糟的 1 过滤掉,只保留我们需要的那 n 列。
         */
        int available = ((1 << n) - 1) & (~(columns | dial | diar));

        // --- 遍历当前行所有可能的解 ---
        // 只要 available 不为 0,说明还有位置没试过
        while (available != 0) {
            
            // 1. 取出最低位的 1 (Lowbit技巧)
            // 这是我们当前决定尝试放置皇后的位置 (例如 00100)
            int pos = available & (-available);

            // 2. 将最低位的 1 置零
            // 表示这个位置我们现在拿去用了,在当前行的后续循环中不要再重复选它
            available = (available & (available - 1));

            // 3. 将二进制位置转换为具体的列索引 (例如 00100 -> 2)
            // 这一步只是为了填 queen 数组生成答案用,不参与核心回溯逻辑
            int column = Integer.bitCount(pos - 1);
            queen[row] = column;

            /* * --- 递归下潜 (Drill Down) ---
             * 这里是最精妙的地方,直接在参数里计算下一行的限制:
             * * 1. columns | pos: 
             * 列限制直接传递,当前列被占,下一行同一列也被占。
             * * 2. (dial | pos) << 1: 
             * 左对角线限制。因为到了下一行,左边的攻击范围会往左偏一格,所以左移。
             * * 3. (diar | pos) >> 1: 
             * 右对角线限制。因为到了下一行,右边的攻击范围会往右偏一格,所以右移。
             */
            dfs(queen, n, row + 1, 
                columns | pos, 
                (dial | pos) << 1, 
                (diar | pos) >> 1);
            
            // --- 恢复现场 ---
            // 虽然 columns/dial/diar 是值传递不需要恢复,但 queen 数组是引用传递
            // 需要重置,虽然后续覆盖写入也没问题,但写上比较规范
            queen[row] = -1;
        }
    }

    // 辅助函数:把数字索引转成字符串棋盘
    public List<String> construct(int[] queen, int n) {
        List<String> curr = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            char[] temp = new char[n];
            Arrays.fill(temp, '.');
            temp[queen[i]] = 'Q';
            curr.add(new String(temp));
        } 
        return curr;
    }
}

思路

回溯中可以说最经典的题,N皇后问题,题解1,博主就是使用了N*N的数组记录皇后位置,导致空间变为O(N2),引以为戒。题解2就做出了优化,因为是每行每行处理,我们只用一个一维数组记录位置就好了,然后额外写一个方法来处理返回值。(博主其实一开始也想用一维数组来处理,但是想着不好返回,就使用了二维数组,脑子没转过来可以写一个处理方法)。

这道题的普通回溯解法配不上困难的词条,我们要挑战自己,就得学习位运算,其实位运算的处理方法并没有提升这道题的思维难度。只是在考察你对位运算的熟练度,无论是与或处理来得到可用范围,还是用getcount巧妙获取当前最后一个1的位置,在思维上都很简单,单纯考你位运算的熟练度,大家不要惧怕,熟练这些操作即可。

相关推荐
灵感__idea4 小时前
Hello 算法:众里寻她千“百度”
前端·javascript·算法
Wect13 小时前
LeetCode 130. 被围绕的区域:两种解法详解(BFS/DFS)
前端·算法·typescript
NAGNIP1 天前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
颜酱1 天前
单调栈:从模板到实战
javascript·后端·算法
CoovallyAIHub2 天前
仿生学突破:SILD模型如何让无人机在电力线迷宫中发现“隐形威胁”
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
从春晚机器人到零样本革命:YOLO26-Pose姿态估计实战指南
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
Le-DETR:省80%预训练数据,这个实时检测Transformer刷新SOTA|Georgia Tech & 北交大
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
强化学习凭什么比监督学习更聪明?RL的“聪明”并非来自算法,而是因为它学会了“挑食”
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
YOLO-IOD深度解析:打破实时增量目标检测的三重知识冲突
深度学习·算法·计算机视觉
NAGNIP2 天前
轻松搞懂全连接神经网络结构!
人工智能·算法·面试