leetcode 51/52N皇后问题(dfs/回溯)

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


上图为 8 皇后问题的一种解法。

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

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

示例:

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

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

  • 解法一:套回溯法模板

解决一个回溯问题,实际上就是一个决策树的遍历过程

参考回溯法模板:

1、路径:也就是已经做出的选择。

2、选择列表:也就是你当前可以做的选择。

3、结束条件:也就是到达决策树底层,无法再做选择的条件。

伪代码回溯的框架:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位,那么N皇后问题则可以看成,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。

那么直接套用框架的话,就是这样:

vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
    // '.' 表示空,'Q' 表示皇后,初始化空棋盘。
    vector<string> board(n, string(n, '.'));
    backtrack(board, 0);
    return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
    // 触发结束条件
    if (row == board.size()) {
        res.push_back(board);
        return;
    }

    int n = board[row].size();
    for (int col = 0; col < n; col++) {
        // 排除不合法选择
        if (!isValid(board, row, col)) 
            continue;
        // 做选择
        board[row][col] = 'Q';
        // 进入下一行决策
        backtrack(board, row + 1);
        // 撤销选择
        board[row][col] = '.';
    }
}

其中对于皇后同一列,对角线,斜对角线的判断可以用isVlalid函数表示:

  • 检查列是否有皇后冲突(很好判断,直接判断某列是否等于Q)
  • 检查右上方是否有皇后冲突(则判断斜对角线是否有皇后,那么斜对角线怎么表示呢,则行索引从row-1递减,列索引从col+1递增)
  • 检查左上方是否有皇后冲突(则判断对角线是否有皇后,与上同)
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
    int n = board.size();
    // 检查列是否有皇后互相冲突
    for (int i = 0; i < n; i++) {
        if (board[i][col] == 'Q')
            return false;
    }
    // 检查右上方是否有皇后互相冲突
    for (int i = row - 1, j = col + 1; 
            i >= 0 && j < n; i--, j++) {
        if (board[i][j] == 'Q')
            return false;
    }
    // 检查左上方是否有皇后互相冲突
    for (int i = row - 1, j = col - 1;
            i >= 0 && j >= 0; i--, j--) {
        if (board[i][j] == 'Q')
            return false;
    }
    return true;
}

那么这两段代码,用Python表示就是这样的(这里需要注意下Python的深拷贝和浅拷贝问题,回溯会修改board的值,需要用深拷贝之前的值):

import copy
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        self.N = n
        self.res = []
        # board = ['.' for i in range(n)]
        board = [['.' for i in range(n)] for j in range(n)]
        self.backtrack(board, 0, self.res)
        return self.res

    def backtrack(self, board, row, res):
        if row == len(board):
            s = []
            for item in board:
                st = ''
                for it in item:
                    st += it
                s.append(st)
            res.append(copy.deepcopy(s))
            return

        n = len(board[row])
        for i in range(n):
            if not self.isValid(board, row, i):
                continue
            board[row][i] = 'Q'
            self.backtrack(board, row + 1, res)
            board[row][i] = '.'

    def isValid(self, board, row, col):
        num = len(board)
        #同一列是否冲突
        for i in range(num):
            if board[i][col] =='Q':
                return False
        i, j = row - 1, col + 1
        #右上方是否冲突
        while i >= 0 and j < num:
            if board[i][j] == 'Q':
                return False
            i -= 1
            j += 1
        #左上方是否冲突
        i, j = row - 1, col - 1
        while i >= 0 and j >= 0:
            if board[i][j] == 'Q':
                return False
            i -= 1
            j -= 1
        return True

但是这样时间复杂度太高了:

执行用时: 108 ms
内存消耗: 13.9 MB
所以我们研究下解法二,直接用dfs,判断皇后是否冲突的时候直接用一维数组判断可好?
  • 解法二:dfs

其实dfs的递归条件,判断条件都可以想得到,而不好理解的地方是哪里呢?就是下面这三个存储N皇后行,对角线,斜对角线是否冲突的数组

        col = [False] * n
        dg = [False] * 2 * n
        xdg = [False] * 2 * n

那么,这里为什么要这么设定需要结合官方题解去理解。对于N皇后是否会在对角线和斜对角线,其实满足以下两种性质:

  • 对角线(第一个图):行下标-列下标=定值
  • 斜对角线(第二个图):行下标+列下标=定值

扩散到其他左上到右下和右上到左下的对角线都有类似性质,因此我们可以直接用一个一维数组去存储每个线是否有皇后的情况(True/False)。

那么为什么要用2n呢,是因为对角线的条数是小于2n的。

 

 

 

 所以,代码是这样的:

import copy
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        grid = [['.' for i in range(n)] for j in range(n)]
        res = []
        col = [False] * n
        dg = [False] * 2 * n
        xdg = [False] * 2 * n

        def dfs(u):
            r = list()
            if u == n:
                for i in range(n):
                    r.append(''.join(copy.deepcopy(grid[i])))
                res.append(r)
                return

            for i in range(n):
                if not col[i] and not dg[u+i] and not xdg[n-u+i]:
                    grid[u][i] = 'Q'
                    col[i] = dg[u+i] = xdg[n-u+i] = True
                    dfs(u+1)
                    grid[u][i] = '.'
                    col[i] = dg[u+i] = xdg[n-u+i] = False
                    
        dfs(0)
        return res

那么N皇后II就很简单了,直接需要返回N皇后的不同排序数量,改下返回值就OK。直接看代码:

class Solution:
    def totalNQueens(self, n: int) -> int:
        grid = [['.' for i in range(n)] for j in range(n)]
        self.res = 0
        col = [False] * n 
        dg = [False] * 2 * n 
        xdg = [False] * 2 * n 

        def dfs(u):
            if u == n:
                self.res += 1
                return 
            for i in range(n):
                if not col[i] and not dg[u+i] and not xdg[n-(u-i)]:
                    grid[u][i] = 'Q'
                    col[i] = dg[u+i] = xdg[n-u+i] = True
                    dfs(u + 1)
                    grid[u][i] = '.'
                    col[i] = dg[u+i] = xdg[n-u+i] = False
        dfs(0)
        return self.res 

参考链接:https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/hui-su-suan-fa-xiang-jie-xiu-ding-ban

https://leetcode-cn.com/problems/n-queens/solution/pythonji-chu-jie-fa-by-lockyguo-111/

 

posted @ 2020-11-28 22:44  Yelush  阅读(163)  评论(0编辑  收藏  举报