【力扣算法】51-N皇后

题目

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

img

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

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

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

示例:

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

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

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

题解

直观想法

第一个想法是使用蛮力法,意味着生成在棋盘上放置 N 个皇后的所有可能的情况,并且检查是否保证没有皇后可以互相攻击。这意味着 O ( N N ) \mathcal{O}(N^N) O(NN)的时间复杂度,因此我们必须考虑优化。

下面是两个有用的编程概念。

第一个叫做 约束编程.

它的基本含义是在放置每个皇后以后增加限制。当在棋盘上放置了一个皇后后,立即排除当前行,列和对应的两个对角线。该过程传递了 约束 从而有助于减少需要考虑情况数。

51_pic.png

第二个叫做 回溯法.

我们来想象一下,当在棋盘上放置了几个皇后且不会相互攻击。但是选择的方案不是最优的,因为无法放置下一个皇后。此时我们该怎么做?回溯。意思是回退一步,来改变最后放置皇后的位置并且接着往下放置。如果还是不行,再 回溯。

51_backtracking_.png

方法1:回溯

在建立算法之前,我们来考虑两个有用的细节。

一行只可能有一个皇后且一列也只可能有一个皇后。

这意味着没有必要再棋盘上考虑所有的方格。只需要按列循环即可。

对于所有的主对角线有 行号 + 列号 = 常数,对于所有的次对角线有 行号 - 列号 = 常数.

这可以让我们标记已经在攻击范围下的对角线并且检查一个方格 (行号, 列号) 是否处在攻击位置。

51_diagonals.png

现在已经可以写回溯函数 backtrack(row = 0).

  • 从第一个 row = 0 开始.
  • 循环列并且试图在每个 column 中放置皇后.
    • 如果方格 (row, column) 不在攻击范围内
      • 在 (row, column) 方格上放置皇后。
      • 排除对应行,列和两个对角线的位置。
      • If 所有的行被考虑过,row == N
        • 意味着我们找到了一个解
      • Else
        • 继续考虑接下来的皇后放置 backtrack(row + 1).
      • 回溯:将在 (row, column) 方格的皇后移除.

下面是上述算法的一个直接的实现。

class Solution {
  int rows[];
  // "hill" diagonals
  int hills[];
  // "dale" diagonals
  int dales[];
  int n;
  // output
  List<List<String>> output = new ArrayList();
  // queens positions
  int queens[];

  public boolean isNotUnderAttack(int row, int col) {
    int res = rows[col] + hills[row - col + 2 * n] + dales[row + col];
    return (res == 0) ? true : false;
  }

  public void placeQueen(int row, int col) {
    queens[row] = col;
    rows[col] = 1;
    hills[row - col + 2 * n] = 1;  // "hill" diagonals
    dales[row + col] = 1;   //"dale" diagonals
  }

  public void removeQueen(int row, int col) {
    queens[row] = 0;
    rows[col] = 0;
    hills[row - col + 2 * n] = 0;
    dales[row + col] = 0;
  }

  public void addSolution() {
    List<String> solution = new ArrayList<String>();
    for (int i = 0; i < n; ++i) {
      int col = queens[i];
      StringBuilder sb = new StringBuilder();
      for(int j = 0; j < col; ++j) sb.append(".");
      sb.append("Q");
      for(int j = 0; j < n - col - 1; ++j) sb.append(".");
      solution.add(sb.toString());
    }
    output.add(solution);
  }

  public void backtrack(int row) {
    for (int col = 0; col < n; col++) {
      if (isNotUnderAttack(row, col)) {
        placeQueen(row, col);
        // if n queens are already placed
        if (row + 1 == n) addSolution();
          // if not proceed to place the rest
        else backtrack(row + 1);
        // backtrack
        removeQueen(row, col);
      }
    }
  }

  public List<List<String>> solveNQueens(int n) {
    this.n = n;
    rows = new int[n];
    hills = new int[4 * n - 1];
    dales = new int[2 * n - 1];
    queens = new int[n];

    backtrack(0);
    return output;
  }
}

复杂度分析

时间复杂度: O ( N ! ) \mathcal{O}(N!) O(N!). 放置第 1 个皇后有 N 种可能的方法,放置两个皇后的方法不超过 N (N - 2) ,放置 3 个皇后的方法不超过 N(N - 2)(N - 4) ,以此类推。总体上,时间复杂度为 O ( N ! ) \mathcal{O}(N!) O(N!) .
空间复杂度: O ( N ) \mathcal{O}(N) O(N) . 需要保存对角线和行的信息。

作者:LeetCode
链接:https://leetcode-cn.com/problems/two-sum/solution/nhuang-hou-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

感想

N皇后不过是在8皇后的基础上修改一下就好了,虽然难度写的是困难,但是学过8皇后后写出个答案应该都不难吧

执行用时 :17 ms, 在所有Java提交中击败了28.56%的用户

内存消耗 :36.4 MB, 在所有Java提交中击败了100.00%的用户

class Solution {
    boolean[][] chessboard;
    int[] yForX;
    List<String> resList;
    List<List<String>> res;
    public List<List<String>> solveNQueens(int n) {
        chessboard = new boolean[n][n];
        yForX = new int[n];
        res = new LinkedList<>();
        placeQueen(0);
        return res;
    }
    private boolean isLegal(int x, int y){
        int n=yForX.length;
        for(int i=1;i<=x&&y>=i;i++){
            if(chessboard[x-i][y-i]) return false;
        }
        for(int i=1;i<n-x&&n-y>i;i++){
            if(chessboard[x+i][y+i]) return false;
        }
        for(int i=1;i<=x&&n-y>i;i++){
            if(chessboard[x-i][y+i]) return false;
        }
        for(int i=1;i<n-x&&y>=i;i++){
            if(chessboard[x+i][y-i]) return false;
        }
        for(int i=0;i<n;i++){
            if(i==y) continue;
            if(chessboard[x][i]) return false;
        }
        for(int i=0;i<n;i++){
            if(i==x) continue;
            if(chessboard[i][y]) return false;
        }
        return true;
    }
    private void placeQueen(int x){
        int n = yForX.length;
        for(int i=0;i<n;i++){
            if(isLegal(x,i)) {
                yForX[x]=i;
                if(x<n-1){
                    chessboard[x][i]=true;
                    placeQueen(x+1);
                    chessboard[x][i]=false;  
                } 
                else {
                    recordChessboard();
                }
            }
        }
    }
    private void recordChessboard(){
        int n =yForX.length;
        StringBuilder sb = new StringBuilder();
        resList = new LinkedList<>();
        for(int i=0;i<n;i++){
            sb.delete( 0, sb.length() );
            for(int j=0;j<n;j++){
                if(j==yForX[i]) sb.append("Q");
                else sb.append(".");
            }
            resList.add(sb.toString());
        }
        res.add(resList);
    }
    
}

后来把isLegal函数优化了一下,可以提高速度:

执行用时 :6 ms, 在所有Java提交中击败了87.44%的用户

内存消耗 :37.5 MB, 在所有Java提交中击败了99.27%的用户

//根据placeQueen是一行一行进行的特点,isLegal只需要检查斜上方和正上方是否有皇后就可以了。
	private boolean isLegal(int x, int y){
        int n=yForX.length;
        for(int i=1;i<=x&&y>=i;i++){
            if(chessboard[x-i][y-i]) return false;
        }
        for(int i=1;i<=x&&n-y>i;i++){
            if(chessboard[x-i][y+i]) return false;
        }
        for(int i=0;i<x;i++){
            if(chessboard[i][y]) return false;
        }
        return true;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值