leetcode
leetcode 2801 ~ 2850
八皇后

八皇后

难度:

标签:

题目描述

Write an algorithm to print all ways of arranging n queens on an n x n chess board so that none of them share the same row, column, or diagonal. In this case, "diagonal" means all diagonals, not just the two that bisect the board.

Notes: This problem is a generalization of the original one in the book.

Example:

 Input: 4
 Output: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
 Explanation: 4 queens has following two solutions
[
 [".Q..",  // solution 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // solution 2
  "Q...",
  "...Q",
  ".Q.."]
]

代码结果

运行时间: 34 ms, 内存: 16.4 MB


/*
 * 题目思路:
 * 1. 使用回溯算法解决N皇后问题。
 * 2. 创建一个二维字符数组棋盘,并初始化为'.'。
 * 3. 定义一个函数isSafe判断某个位置是否可以放置皇后。
 * 4. 使用递归函数solveNQueens进行回溯搜索。
 * 5. 如果找到解决方案,将其添加到结果集中。
 * 6. 使用Java Streams处理结果。
 */
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class NQueensStream {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> res = new ArrayList<>();
        char[][] board = new char[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                board[i][j] = '.';
            }
        }
        solve(res, board, 0, n);
        return res;
    }

    private void solve(List<List<String>> res, char[][] board, int row, int n) {
        if (row == n) {
            res.add(construct(board));
            return;
        }
        for (int col = 0; col < n; col++) {
            if (isSafe(board, row, col, n)) {
                board[row][col] = 'Q';
                solve(res, board, row + 1, n);
                board[row][col] = '.';
            }
        }
    }

    private boolean isSafe(char[][] board, int row, int col, int n) {
        for (int i = 0; i < row; i++) {
            if (board[i][col] == 'Q') return false;
        }
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] == 'Q') return false;
        }
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (board[i][j] == 'Q') return false;
        }
        return true;
    }

    private List<String> construct(char[][] board) {
        return java.util.Arrays.stream(board)
                .map(String::new)
                .collect(Collectors.toList());
    }
}

解释

方法:

这个题解使用了回溯法和位运算来解决 N 皇后问题。算法的核心是通过递归尝试在每一行放置一个皇后,并使用位运算来高效地检查是否满足不同行、不同列和不同对角线的约束。其中,column、diag1 和 diag2 分别用来表示已占用的列、主对角线和副对角线。avail 变量用来表示当前行中可放置皇后的位置。通过位运算,算法高效地找到可放置皇后的位置,并递归地在下一行继续放置皇后。

时间复杂度:

O(N! * N)

空间复杂度:

O(N)

代码细节讲解

🦆
在算法中,位运算是如何用来检查皇后是否与其他皇后在同一列或对角线上的?
在算法中,位运算通过维护三个位掩码:`column`、`diag1` 和 `diag2`来检查约束。`column`位掩码用于标记已放置皇后的列;`diag1`位掩码用于标记主对角线(从左上到右下),每次向左移位(<<1)来更新当前行的主对角线约束;`diag2`位掩码用于标记副对角线(从右上到左下),每次向右移位(>>1)来更新当前行的副对角线约束。每行递归尝试放置皇后时,会计算`avail`变量,这个变量通过位运算`(~(column | diag1 | diag2)) & ((1 << n) - 1)`得出当前行哪些位置是可用的(即不在先前皇后的攻击范围内)。
🦆
为什么在算法中使用位运算可以提高效率,相比于其他可能的检查方法(如使用二维数组)有什么优势?
使用位运算提高效率的主要原因是位运算可以在硬件级别上直接处理整数的位模式,这比在高级语言中处理数组或其他数据结构要快得多。位运算允许我们在常数时间内进行检查和更新操作,例如检查冲突、设置和清除位。与使用二维数组相比,位运算减少了存储空间的需求,因为它仅使用几个整数来表示所有的列和对角线状态,而不需要为棋盘的每个单元格分别存储状态。这样不仅提高了空间效率,还由于减少了内存访问,提高了时间效率。
🦆
在递归函数`solve`中,如何确定变量`avail`代表的可用位置?
在递归函数`solve`中,`avail`变量代表当前行中可放置皇后的位置。它的计算方式为`((1 << n) - 1) & (~(column | diag1 | diag2))`。这里,`(1 << n) - 1`生成一个长度为n的位掩码,所有位均为1,表示所有列理论上都是可放置的位置。`~(column | diag1 | diag2)`通过对已占用的列和对角线的位掩码取反,得到一个新的位掩码,其中1表示未被占用的列和对角线。最后通过这两个掩码的与操作,确保只考虑棋盘大小内的位置。这样,`avail`中的每个1都代表对应的列可以安全放置一个皇后。

相关问题