LeetCode—51.N皇后

文章同步发布在我的个人博客(zhuoerhuobi.cn)

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

LeetCode—51.N皇后_第1张图片
(上图为 8 皇后问题的一种解法。)

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

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

示例:

输入: 4
输出: [
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],

["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解释: 4 皇后问题存在两个不同的解法。

提示:

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

思路

N皇后经典递归算法题,写了无数遍。新手可以从这道题里学到很多回溯、标记、DFS等基础却又很有用的概念。

研究算法的本质是使用更短的时间和更小的空间解决问题。N皇后最简单的思路就是放上N个棋子判断,但是复杂度是O(n^n),明显不可取。如何降低复杂度?

可以很容易想到一种方法,我随意放置一个棋子,放下去以后就会有部分格子不能放棋子了(会被吃),那我接下来的选择是不是就变少了,越往后选择余地就越小,也就大大降低了时间复杂度。如何知道某个格子能不能放棋子?所以我们就会接触到染色标记)的概念,也就是给不能放的格子进行标记,一个听起来平平无奇却又几乎贯彻在所有算法中的小技巧。

接下来还能怎么优化?我们又想到,每一行必定只能放一个皇后,所以我们只要关注接下来没放棋子的行,并且只需要关注皇后竖向和斜向能吃到哪里。在某行如果没有可以放棋子的格子怎么办?所以我们需要回溯。回溯就需要返回之前的状态,如何返回?所以我们需要在DFS前保存状态,DFS后还原状态,也就类似于

Java实现

class Solution {
    static int N;
    static List<List<String>> res;
    static String[] chessBoard;
    static int[][] color;
    
    public List<List<String>> solveNQueens(int n) {
        res = new ArrayList<>();
        chessBoard = new String[n];
        color = new int[n+1][n+1];
        N = n;
        dfs(1);
        return res;
    }

    public static void dfs(int row) {
		//递归都要有边界条件。
        if (row > N) {
            res.add(new ArrayList<>(Arrays.asList(chessBoard)));
            return;
        }
        for (int i = 1; i <= N; i++) {
            if (color[row][i] == 0) {
                dyeing(row, i);
                dfs(row+1);
                fade(row, i); //dfs完要还原状态,这里的操作是不是很像“锁”?
            }
        }
    }

    public static void dyeing(int row, int column) {
        StringBuilder temp = new StringBuilder();
        for (int i = 0; i < N; i++) {
            if (i == column-1) {
                temp.append('Q');
            }
            else {
                temp.append('.');
            }
        }
        chessBoard[row-1] = temp.toString();
        for (int i = row+1; i <= N; i++) {
            color[i][column]++;
        }
        for (int i = 1; Math.max(row, column)+i <= N; i++) {
            color[row+i][column+i]++;
        }
        for (int i = 1; row+i <= N && column-i > 0; i++) {
            color[row+i][column-i]++;
        }
    }

    public static void fade(int row, int column) {
        chessBoard[row-1] = "";
        for (int i = row+1; i <= N; i++) {
            color[i][column]--;
        }
        for (int i = 1; Math.max(row, column)+i <= N; i++) {
            color[row+i][column+i]--;
        }
        for (int i = 1; row+i <= N && column-i > 0; i++) {
            color[row+i][column-i]--;
        }
    }
}

从一开始的摸不着头脑,到如今的驾轻就熟,每次做N皇后我都有不同的感受。其实题刷多了,会发现都是那些套路(不包括少部分难题)。所以刷题不在于多,一定要在刷的过程中注意总结,递归、贪心、DP、DFS、BFS、回溯这些概念要烂熟于心。做题时要把握住题目的本质,理解它到底是在考察什么知识。

题刷百遍,其意自现。

你可能感兴趣的:(算法-java)