51. N 皇后

51. 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 1 \le n \le 9 1n9
  • 皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。

题解:

法一:

普通回溯。

这题的关键是在搜索时候怎么快速判断 行、列、对角线 上是否已经放置了皇后。

假设当前位置为 (r, c) ,行和列就不用说了,使用两个数组记录就行了,而对角线比较麻烦。

但是,从左下到右上的每条对角线,可以发现对角线上的点都满足 n - r + c 是定值且唯一,也就意味着我们可以开辟一个数组,根据 n - r + c 作为下标来记录对角线上是否已经放置皇后了。同理左上到右下的每条对角线,对角线上的点都满足 r + c 是定值且唯一,我们可以开辟一个数组来记录放置皇后的状态。

这样的话,我们需要 4 个数组来记录状态。那么可以写出来一份 很蠢的代码

时间复杂度: O ( 2 n 2 ) O(2^{n^2}) O(2n2)

class Solution {
    
    
public:
    vector<bool> row, col, diag, rdiag;
    vector<vector<string>> ret;
    vector<string> ans;

    void dfs( int x, int y, int num, int n ) {
    
    
        if ( y == n ) ++x, y = 0;
        if ( x >= n ) {
    
    
            if ( num >= n ) ret.push_back( ans );
            return;
        }
        dfs( x, y + 1, num, n );
        if ( !row[x] && !col[y] && !diag[n - x + y] && !rdiag[x + y] ) {
    
    
            row[x] = col[y] = diag[n - x + y] = rdiag[x + y] = true;
            ans[x][y] = 'Q';
            dfs( x, y + 1, num + 1, n );
            row[x] = col[y] = diag[n - x + y] = rdiag[x + y] = false;
            ans[x][y] = '.';
        }
    }
    vector<vector<string>> solveNQueens(int n) {
    
    
        ans = vector<string>( n, string(n, '.') );
        row = vector<bool>( n );
        col = vector<bool>( n );
        diag = vector<bool>( n << 1 );
        rdiag = vector<bool>( n << 1 );
        dfs( 0, 0, 0, n );
        return ret;
    }
};
/*
时间:240ms,击败:5.26%
内存:8.6MB,击败:31.52%
*/

可以看到,速度出奇的慢2333。

优化:

其实可以只使用 3 个数组就行了,我们可以按 行优先,依次在每行中放置一个皇后,可以省去一个数组。而且时间复杂度变为 O ( n ! ) O(n!) O(n!)

class Solution {
    
    
public:
    vector<bool> col, diag, rdiag;
    vector<vector<string>> ret;
    vector<string> ans;

    void dfs( int row, int n ) {
    
    
        if ( row >= n ) {
    
    
            ret.push_back( ans );
            return;
        }
        for ( int i = 0; i < n; ++i ) {
    
    
            if ( col[i] || diag[n - row + i] || rdiag[row + i] ) continue;
            ans[row][i] = 'Q';
            col[i] = diag[n - row + i] = rdiag[row + i] = true;
            dfs( row + 1, n );
            ans[row][i] = '.';
            col[i] = diag[n - row + i] = rdiag[row + i] = false;
        }
    }
    vector<vector<string>> solveNQueens(int n) {
    
    
        ans = vector<string>( n, string(n, '.') );
        col = vector<bool>( n );
        diag = vector<bool>( n << 1 );
        rdiag = vector<bool>( n << 1 );
        dfs( 0, n );
        return ret;
    }
};
/*
时间:0ms,击败:100.00%
内存:7.2MB,击败:96.69%
*/

再优化:

由于 1 ≤ n ≤ 9 1 \le n \le 9 1n9 ,可以不开辟额外的数组,因为 int 变量除去符号位,有 31 位,而我们的对角线下标最多为 r + c = 9 + 9 = 18 ,使用 int 变量来记录状态完全可行。

class Solution {
    
    
public:
    int col, diag, rdiag;
    vector<vector<string>> ret;
    vector<string> ans;

    void dfs( int row, int n ) {
    
    
        if ( row >= n ) {
    
    
            ret.push_back( ans );
            return;
        }
        for ( int i = 0; i < n; ++i ) {
    
    
            if ( (col >> i & 1) || (diag >> (n - row + i) & 1) || (rdiag >> (row + i) & 1) ) continue;
            ans[row][i] = 'Q';
            col ^= 1 << i;
            diag ^= 1 << (n - row + i);
            rdiag ^= 1 << (row + i);
            dfs( row + 1, n );
            ans[row][i] = '.';
            col ^= 1 << i;
            diag ^= 1 << (n - row + i);
            rdiag ^= 1 << (row + i);
        }
    }
    vector<vector<string>> solveNQueens(int n) {
    
    
        ans = vector<string>( n, string(n, '.') );
        col = diag = rdiag = 0;
        dfs( 0, n );
        return ret;
    }
};
/*
时间:0ms,击败:100.00%
内存:7.3MB,击败:94.61%
*/

不过相比于开辟的 bool 数组,空间优化不明显。

法二:

方法一 中,一直都有同一个限制效率的因素:在搜索树的深处,能够放皇后的位置其实很少了,但我们一直都要试探每一列。

能不能直接得知每一行上还有哪些列能够放置皇后,免去这个枚举的过程呢?

位运算恰恰提供了这个可能!

使用三个变量 row, diag, rdiag

  • row 表示当前行哪些列已经被占用
  • diagrdiag 表示当前行中,哪些对角线被其它行影响不能放皇后
  • 初始时 row = diag = rdiag = 0

比如:第 x 行有一个位置放了一个皇后:

row = 0 0 1 0 0 0 0 0

那么在第 x+1 行中,1 的左下方和右下方都不能在放置皇后。也就是:

diag = 0 0 0 1 0 0 0 0
rdiag= 0 1 0 0 0 0 0 0

并且,diag = row >> 1rdiag = row << 1,可直接通过移位得到。

也就是说,如果第 x 行放的皇后位置为 p = 1 << k ,那么下一行中受 p 的影响,这些对角线是不能放皇后的:

diag = (diag | p) >> 1
rdiag= (rdiag| p) << 1

而在第 x 行,可放皇后的位置可以通过下面的计算公式直接得到:

safe = ((1 << n) - 1) & (~(row | diag | rdiag))

二进制表示中,1 表示可以放置,0 表示不可以放置,剩下的就是枚举 safe 中的 1

如果像下面这样的求 safe 中的每一位:

while ( x ) {
    
    
    if ( x & 1 ) do {
    
     ... };
    x >>= 1;
}

根本没法发挥出位运算的功效。

lowbit(x) = x & -x 恰好能求 x 的二进制表示中最后一位 1 代表的十进制数。可以这么求:

while ( x ) {
    
    
    int nxt = x & -x;
    do {
    
     ... };
    x ^= nxt;
}

这样的话,我们就可以快速枚举可以放皇后的位置了。

最终的状态是: row = (1 << n) - 1,表示所有列都放了一个皇后,找到了一个可行解。

class Solution {
    
    
public:
    int col, diag, rdiag;
    vector<vector<string>> ret;
    vector<string> ans;
    vector<int> map;
    int goal;

    void dfs( int r, int row, int diag, int rdiag ) {
    
    
        if ( row == goal ) {
    
    
            ret.push_back( ans );
            return;
        }
        int safe = goal & (~(row | diag | rdiag));
        while ( safe ) {
    
    
            int nxt = safe & -safe;
            safe ^= nxt;
            ans[r][map[nxt]] = 'Q';
            dfs( r + 1, row | nxt, (diag | nxt) << 1, (rdiag | nxt) >> 1 );
            ans[r][map[nxt]] = '.';
        }
    }
    vector<vector<string>> solveNQueens(int n) {
    
    
        ans = vector<string>( n, string(n, '.') );
        goal = (1 << n) - 1;
        map.resize( 1 << n );
        for ( int i = 0; i < n; ++i ) map[1 << i] = i;
        dfs( 0, 0, 0, 0 );
        return ret;
    }
};
/*
时间:0ms,击败:100.00%
内存:7.1MB,击败:98.22%
*/

猜你喜欢

转载自blog.csdn.net/MIC10086/article/details/113177601