zoukankan      html  css  js  c++  java
  • 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/

  • 相关阅读:
    暑假练习:游戏
    Floyd算法 笔记 C/C++
    Bellman-Ford 与 SPFA 算法笔记
    Dijkstra算法 C++
    C/C++ 并查集及其优化笔记整理
    C/C++ 哈夫曼树与哈夫曼编码
    判断是否为同一颗搜索树 C/C++
    C/C++ 平衡二叉树笔记(AVL树)
    VB中 “实时错误“3704”,对象关闭时,不允许操作”
    SQL Server 2014 配置全过程
  • 原文地址:https://www.cnblogs.com/yeshengCqupt/p/14054674.html
Copyright © 2011-2022 走看看