【LeetCode】N 皇后(回溯)

166 篇文章 1 订阅

51. N 皇后 - 力扣(LeetCode)

一、题目

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

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

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

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

示例 1:

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

示例 2:
输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

二、代码

class Solution {
    public List<List<String>> solveNQueens(int n) {
        // 记录所有的放置方法
        List<List<String>> ans = new ArrayList<>();
        // 记录尝试过程中,已放置的皇后位置
        int[] queenCoordinate = new int[n];
        // 递归入口
        process(queenCoordinate, n, 0, ans);
        return ans;
    }

    /*
        在传参中带了List<List<String>> ans(地址传递),每一种可行的放置方法会存入这个对象中
     */
    public void process(int[] queenCoordinate, int n, int row, List<List<String>> ans) {
        // 当讲所有的行都遍历完成,说明这个递归分支的放置方法是可行的
        if (row == n) {
            // 当前方法放置皇后的坐标都在queenCoordinate变量中,根据这个变量将放置图写入到ans中
            addQueenCoordinate(ans, queenCoordinate, n);
            // 返回
            return;
        }

        // 在第row行,尝试所有的列,看看是不是有效的放置皇后的位置(不与已放置的皇后起冲突)
        for (int i = 0; i < n; i++) {
            // 这里就是尝试过程中的无效检测,如果尝试的这个位置本身就是一个无效位置,那么直接就不会进入向下的递归,也就中断了。
            if (isValid(row, i, queenCoordinate)) {
                // 如果当前尝试的这个位置有效,那么就将这个位置设置成为皇后放置位置,然后以这个位置为基准,继续向下层递归,去下一行row + 1尝试
                queenCoordinate[row] = i;
                process(queenCoordinate, n, row + 1, ans);
                // 注意在找到一种放置方案后,递归开始返回,需要还原queenCoordinate的数据,如果不清除上一种放置方法在queenCoordinate中存储的皇后位置,在后面的方法向里面存储皇后放置位置的时候,就会出现混乱的情况,导致两种方法的结果都混在了同一个queenCoordinate中,答案就不对了,所以这种就需要还原现场。
                queenCoordinate[row] = 0;
            }
            
        }
        return;
    }

    // 判断当前选择的这个摆放位置是不是有效的,会不会和已有的皇后起冲突
    public boolean isValid(int x, int y, int[] queenCoordinate) {
        // 0..i-1
		// 传入的x和y就是要尝试位置的坐标,这个方法就是看这个坐标是不是有效的
		// 遍历尝试的这一层以上的所有放置的皇后位置,然后去看尝试位置是不是与这些皇后位置冲突
        for (int i = 0; i < x; i++) {
            // 不用考虑行冲突,因为已经限定死了,每一行有且只能放一个皇后
			// 只要纵坐标不相等,这两个位置就没有列冲突
			// 去检查两个位置的横纵坐标相减的绝对值是不是一样,如果一样说明两个位置在同一条斜线上,就算是起冲突了。其实这个本质就是算两个位置与远点连线的斜率是否一致
            int a = i;
            int b = queenCoordinate[i];
            if ((Math.abs(x - a) == Math.abs(y - b)) || y == b) {
                return false;
            }
        }
        
        return true;
    }

    // 根据queenCoordinate中存储的坐标位置,转换成放置图示存入ans中
    // 每执行一次该方法,就会存储一种对整个棋盘的有效放置方法
    public List<List<String>> addQueenCoordinate(List<List<String>> ans, int[] queenCoordinate, int n) {
        // 对整个期盼的有效放置方法就存在rowAns中
        List<String> rowAns = new ArrayList<>();
        // 按棋盘从上到下,从左到右遍历
        for (int row = 0; row < n; row++) {
            // 棋盘每一行的放置情况就是存入一个String
            StringBuilder sb = new StringBuilder();
            for (int i = 0 ; i < n; i++) {
                // 未放置皇后的位置赋值"."
                if (queenCoordinate[row] != i) {
                    sb.append(".");
                // 放置皇后的位置赋值"Q"
                } else {
                    sb.append("Q");
                }
            }
            rowAns.add(sb.toString());
        }
        // 将当前找出的放置方法存入到ans中
        ans.add(rowAns);
        return ans;
    }
}

三、解题思路 

这个题是一道非常经典的题目,整体就是使用暴力枚举的方法来求解,尝试所有的可能的放置情况,通过递归去做尝试,最终找到符合条件的放置方法。

这里就规定每一行有且只能放一个皇后,这样就可以不用考虑行冲突了,只需要考虑列和斜线冲突就行了。列冲突就是看纵坐标是否相同,斜线冲突就是看两个位置与原点连线的斜率是否相同,相同的话就是斜线冲突。按照这个规则就去尝试每一行的每一列,如果冲突了就跳过,如果没有起冲突就选定该位置,然后向下递归去尝试下一行。直到所有的位置都尝试一遍,整个过程就算是完事了。

时间复杂度:O(N^N)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值