力扣第五十一题-N 皇后

394 阅读3分钟

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

前言

力扣第五十一题 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"]]

提示:

  • 1 <= n <= 9
  • 皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。

一、思路

N 皇后 是一道非常经典的 回溯算法 题目,值得一做!

tips:这一题和 力扣第三十七题-解数独 非常的像,思路几乎是一样的,有兴趣的也可以看一下那题。

题目中有三个非常重要的信息:

  • 皇后 彼此不能在同一行
  • 皇后 彼此不能在同一列
  • 皇后 彼此不能在同一斜线

大致的思路分为以下几个部分:

  1. 使用三个二维数组存储已选择的点在行列及斜线的占用情况
  2. 因为每一行都会有一个 皇后,所以当前递归中只遍历当前行的元素(剪枝)

图解算法

在实现的过程中,踩坑最多的地方就是存储斜线的占用情况了,所以下面会主要介绍是如何存储行、列及斜线的占用情况的。

至于递归的话,思路不是很难,精简后的伪代码如下所示:

    public void dfs(List<List<String>> ret, int count) {
        if (count == len) {
            添加结果到ret中
            return;
        }
        // 每一行都会有一个皇后,所以只需遍历n次
        for (int i=0; i<len; i++) {
            // 判断是否可以放置皇后
            if (!rowFlags[count][i] && !colFlags[count][i] && slashFlags[count][i] == 0) {
                更新占用情况
                // 向下递归
                dfs(ret, count+1);
                恢复占位情况
            }
        }
    }

变量说明:
boolean[][] positions; // 选择的元素
boolean[][] rowFlags; // 行占用情况
boolean[][] colFlags; // 列占用情况
int[][] slashFlags; // 斜线占用情况

如下图所示,当选择 第一行第二列 作为第一个元素时,即positions[0][1] = true

image.png

行、列占用情况

行和列占用可以使用两个 二维布尔数组 来存储

此时存储情况为 rowFlags[0][i] 均为 truecolFlags[i][1] 均为 true

斜线占用情况

我开始也是用 二维布尔数组 来存储斜线占用情况的,但是我发现会有相互干扰的情况

如下图所示,[3, 4][4, 1] 斜线占用情况会有重合的情况,当向上回溯时就会将重合的地方置为false

黄色表示选择的元素,浅色表示占用的斜线情况 image.png

所以斜线占用可以试用 二维int数组 来存储,占用就 +1,回溯时就 -1

此时存储情况为 slashFlags[1][0]slashFlags[0][1]slashFlags[1][2]slashFlags[2][3]slashFlags[3][4] 均为 1

二、实现

实现代码

实现代码与思路中保持一致(就是代码有点长了tnt~)

    boolean[][] positions;
    boolean[][] rowFlags;  // 行
    boolean[][] colFlags;  // 列
    int[][] slashFlags;  // 斜线
    int len;

    /**
     * 需要分别存储行、列、斜线的状态,不然会相互影响
     * @param n
     * @return
     */
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> ret = new ArrayList<>();
        rowFlags = new boolean[n][n];
        colFlags = new boolean[n][n];
        slashFlags = new int[n][n];
        positions = new boolean[n][n];
        len = n;
        dfs(ret, 0);
        return ret;
    }

    /**
     * 递归
     */
    public void dfs(List<List<String>> ret, int count) {
        if (count == len) {
            List<String> list = new ArrayList<>();
            // 记录结果
            for (int i=0; i<len; i++) {
                StringBuilder sb = new StringBuilder();
                for (int j=0; j<len; j++) {
                    if (positions[i][j])
                        sb.append("Q");
                    else
                        sb.append(".");
                }
                list.add(sb.toString());
            }
            ret.add(list);
            return;
        }

        // 每一行都会有一个皇后,所以只需遍历n次
        for (int i=0; i<len; i++) {
            // 判断当前列是否可以放置皇后
            if (!rowFlags[count][i] && !colFlags[count][i] && slashFlags[count][i] == 0) {
                // 更新
                positions[count][i] = true;
                updateFlags(count, i, true);
                // 向下递归
                dfs(ret, count+1);
                positions[count][i] = false;
                updateFlags(count, i, false);
            }
        }
    }

    public void updateFlags(int row, int col, boolean flag) {
        // 更新行
        for (int i=0; i< len; i++) {
            rowFlags[row][i] = flag;
        }
        // 更新列
        for (int i=0; i<len; i++) {
            colFlags[i][col] = flag;
        }
        // 更新斜线
        int tRow = row; // 起始点(斜向下)
        int tCol = col;
        while (tRow>0 && tCol >0) {
            tRow--;
            tCol--;
        }
        // 更新斜向下
        while (tRow<len && tCol<len) {
            if (flag) {
                ++slashFlags[tRow][tCol];
            } else if (slashFlags[tRow][tCol] > 0){
                --slashFlags[tRow][tCol];
            }
            tRow++;
            tCol++;
        }
        tRow = row; // 起始点(斜向上)
        tCol = col;
        while (tRow>0 && tCol<len-1) {
            tRow--;
            tCol++;
        }
        // 更新斜向上
        while (tRow<len && tCol>-1) {
            if (flag) {
                ++slashFlags[tRow][tCol];
            } else if (slashFlags[tRow][tCol] > 0){
                --slashFlags[tRow][tCol];
            }
            tRow++;
            tCol--;
        }
    }

测试代码

    public static void main(String[] args) {
        new Number51().solveNQueens(8);
    }

结果

image.png

三、总结

下一题是 N 皇后 II,题目基本是一样的,会再将本文的思路优化一下。

感谢看到最后,非常荣幸能够帮助到你~♥