[回溯法] -- 52 - N皇后Ⅱ - Java

105 阅读3分钟

[回溯法] – 46 - 全排列 - python + Java
[回溯法] – 47 - 全排列Ⅱ - python + Java


N皇后

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


在这里插入图片描述

例如,上图为 8 皇后问题的一种解法。给定一个整数 n,返回 n 皇后不同的解决方案的数量。

输入: 4
输出: 2
解释: 4 皇后问题存在如下两个不同的解法。
[
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 

提示:皇后是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一到七步,可进可退。(引用自 百度百科 - 皇后 )


N皇后问题是典型的回溯问题,题目的解只能枚举所有的情况,最后从中选择出所有可能的结果。根据题目的描述可知,如果棋盘上的某个格子中已经存在了一个皇后,那么它的同行同列,以及正负对角线上都不应该存在其他的皇后。因此,可以知道结果的数目是固定,可以通过回溯法找到所有可能的结果。

假设某个时刻棋盘中皇后的摆放如下所示:
在这里插入图片描述
此时[0, 1][1, 3]都已经放上了皇后,且此时的棋盘是不会出现冲突的。考虑第2行的摆放,根据合法性的原则,只能在[2, 0]处放置一个皇后。最后考虑当前棋盘下,最后一行应该如何放置,只能依次进行判断:

  • [3, 0]:它会与[2, 0]的皇后发生冲突,所以不合法
  • [3, 1]:它会和其他三个已经摆放的皇后都发生冲突,所以同样不合法
  • [3, 2]:它和已放的皇后都不冲突,因此是一个合法位置
  • [3, 3]:根据原则可知,前面已有合法的位置,那么此时后面的位置其实都可以不再进行判断

因此,根据图示的过程我们可以很容易将其归纳到回溯的框架中,判断的顺序可以为:

  • 当前行标下所有列的摆放
  • 当前位置的正对角线(↗)的摆放
  • 当前位置负对角线(↖)的摆放

题目的分析和解题思路和51题是几乎一样的,不同之处在于最后只需要统计合法结果的数量即可,而无需保存可能的的摆放情况。Java解题代码如下:

class Solution {

    int count = 0;

    public int totalNQueens(int n) {
        // 初始化棋盘
        char[][] map = new char[n][n];
        for (char[] chars : map) {
            Arrays.fill(chars, '.');
        }

        // 以行开始进行判断,当然以列也可以
        int row = 0;

        // 深度优先搜索
        dfs(map, row);

        return count;
    }

    public void dfs(char[][] map, int row){
        int length = map.length;

        // 终止条件,如果到达最后一行,判断结束
        if(row == length){
            count += 1;
            return;
        }

        // 选择过程
        for (int col = 0; col < length; col++) {
        	// 判断棋盘上当前位置是否合法
            if(!isValid(map, row, col)){
                continue;
            }

            // 如果合法,做出选择,判断下一行可能的摆放情况
            map[row][col] = 'Q';
            // 回溯
            dfs(map, row + 1);
            // 撤销选择
            map[row][col] = '.';
        }
    }

    private boolean isValid(char[][] map, int row, int col) {
        int length = map.length;

        // 判断当前行、列的位置是否合法
        for (char[] chars : map) {
            // 如果其他行的该列上已经放了皇后,则不能再放,说明当前位置不合法
            if (chars[col] == 'Q') return false;
        }

        // 判断右上对角线情况,行减列加
        for (int i = row - 1, j = col + 1; i >= 0 && j < length; i--, j++) {
            // 如果对角线上已经放了,该位置不合法
            if (map[i][j] == 'Q') return false;
        }

        // 检查左上对角线,行减列减
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
             // 如果对角线上已经放了,该位置不合法
            if (map[i][j] == 'Q') return false;
        }
        // 如果该列的所有行和所有对角线上都没有放,则说明为合法位置
        return true;
    }
}