LeetCode N皇后

题目描述:

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

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

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

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

示例 1:
在这里插入图片描述
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

解题思路:

通过递归和回溯的方式,尝试所有可能的皇后放置方法,直到找到所有合法的解决方案。
以下是解决N皇后问题的基本步骤和思路:

  1. 问题定义
    在N×N的棋盘上放置N个皇后,使得它们互不攻击,即任何两个皇后都不在同一行、同一列或同一对角线上。

  2. 初始化棋盘
    创建一个N×N的二维数组board,用.表示空位,用'Q'表示皇后的位置,初始时所有位置都是空的。

  3. 定义合法性检查函数isValid):
    编写一个函数,检查在棋盘的(row, col)位置放置皇后是否会导致攻击冲突。这通常通过检查:

    • 当前列是否已有皇后。
    • 左上对角线(i - j)和右上对角线(i + j)是否有皇后。
  4. 递归放置函数helper):
    编写一个递归函数,从棋盘的第一行开始尝试放置皇后:

    • 如果当前行数等于N,表示所有皇后都已放置完毕,记录当前棋盘状态为一个解决方案。
    • 对当前行的每一列调用isValid函数检查是否可以放置皇后。
    • 如果可以放置,将皇后放在该位置,然后递归调用helper函数尝试放置下一行的皇后。
    • 如果下一行没有找到合法位置放置皇后,或者已经放置完所有皇后但不是有效的解决方案,递归返回并进行回溯。
  5. 回溯
    helper函数在某一位置找不到合法的皇后放置位置时,它会回溯到上一行,将之前放置的皇后移除(用.替换'Q'),然后尝试该行的其他列位置。

  6. 收集解决方案
    每当找到一个合法的棋盘布局时,就将其添加到解决方案数组res中。

  7. 开始递归
    从棋盘的第一行(通常是0行)开始调用helper函数,尝试放置第一个皇后。

  8. 返回所有解决方案
    当所有可能的放置方式都被探索后,函数返回包含所有解决方案的数组。

  9. 输出或使用解决方案
    根据需要,可以打印或以其他方式使用找到的所有解决方案。

这个算法的关键在于递归和回溯的结合使用,它允许程序系统地探索所有可能的解决方案,同时在遇到当前路径不可能形成解决方案时及时回溯,避免无效搜索。

代码

/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function (n) {
        // 初始化棋盘为n×n的二维数组,用'.'表示空位
        const board = new Array(n);
        for (let i = 0; i < n; i++) {
            board[i] = new Array(n).fill('.'); // 每一行都是n个'.'字符的数组
        }

        // 初始化一个数组用来存放所有解决方案
        const res = [];

        // 定义一个函数来检查在给定位置(row, col)放置皇后是否合法
        const isValid = (row, col) => {
            // 检查之前的行是否有皇后在同一列或对角线上
            for (let i = 0; i < row; i++) {
                for (let j = 0; j < n; j++) {
                    // 如果之前行的某个位置是皇后,并且与当前位置在同一列或对角线上
                    if (board[i][j] === 'Q' &&
                        (j === col || // 同一列
                            i - j === row - col || // 左上对角线
                            i + j === row + col)) { // 右上对角线
                        return false; // 放置不合法
                    }
                }
            }
            return true; // 放置合法
        };

        // 定义一个递归函数来尝试放置皇后
        const helper = (row) => {
            // 如果当前行数等于n,说明已经放置完所有皇后,找到一个解决方案
            if (row === n) {
                // 将当前棋盘状态复制并保存为解决方案
                res.push(board.map(row => row.join('')));
                return;
            }
            // 尝试在当前行的每个位置放置皇后
            for (let col = 0; col < n; col++) {
                // 如果放置皇后的位置是合法的
                if (isValid(row, col)) {
                    // 在当前位置放置皇后
                    board[row][col] = "Q";
                    // 递归放置下一行的皇后
                    helper(row + 1);
                    // 回溯,移除当前位置的皇后,尝试其他位置
                    board[row][col] = '.';
                }
            }
        };

        // 从棋盘的第一行开始放置皇后
        helper(0);

        // 返回所有找到的解决方案
        return res;
    };

代码注解

board[i] = new Array(4).fill('.') 

这行代码创建了一个长度为4的一维数组,并且使用 .fill('.') 方法将这个数组的每个元素都设置为字符串 '.'。这个数组代表棋盘的一行。

所以,如果我们逐行看这个数组,它的形状是这样的:

[
  [ '.', '.', '.', '.' ], // 第0行
  [ '.', '.', '.', '.' ], // 第1行
  [ '.', '.', '.', '.' ], // 第2行
  [ '.', '.', '.', '.' ], // 第3行
]

这里,每个子数组 [ '.', '.', '.', '.' ] 都是 board 数组的一个元素,代表棋盘上的一行,每个元素 '.' 代表棋盘上的一个空位。整个 board 数组是一个二维数组,有4行4列,总共16个元素。

提示:

在N皇后问题的递归回溯算法中,如果在最后一行没有找到放置皇后的合法位置,递归调用会正常返回,但不会向解决方案数组 res 添加任何内容。这是因为只有在成功放置了所有N个皇后,并且所有皇后都不相互攻击时,才会记录解决方案。

这里是递归调用的逻辑流程:

  1. 递归调用 helper(row + 1) 尝试在下一行放置皇后。
  2. 如果在当前行(row)成功放置了一个皇后,递归进入下一行。
  3. 在下一行,继续尝试放置皇后,重复步骤1和2。
  4. 如果在任何行找不到合法的皇后位置,或者违反了皇后放置规则,递归调用会结束,并开始回溯。
  5. 在回溯过程中,helper(row + 1) 返回到上一次递归调用的状态,此时会执行 board[row][col] = '.'; 来撤销当前行 col 列的皇后放置。
  6. 外层循环(for (let col = 0; col < n; col++))会继续尝试当前行的下一个列位置。

如果在最后一行(row + 1 == n)没有找到合法的皇后位置,递归调用会一直回溯到更早的行,直到找到可以放置皇后的位置,或者确定当前部分解决方案不可行。在后一种情况下,递归会一直回溯到最开始,不会产生任何解决方案。

重要的是,递归调用本身不会返回任何“值”,它通过修改全局状态(即棋盘 board)来进行工作。解决方案是通过检查棋盘状态来收集的,只有当所有N个皇后都合法放置时,才会将棋盘状态添加到解决方案数组 res 中。如果递归到达最后一行但没有找到解决方案,res 数组不会受到影响,仍然保持之前的状态。

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值