leetcode 51.N-Queens【递归、位运算】【hard】|刷题打卡

433 阅读4分钟

一、题目描述:

leetcode 51.N-Queens: n皇后问题,要求返回指定格式的二维列表,其中“Q”表示皇后的位置,“.”表示皇后不在的位置。

二、思路分析:

1.类别

递归、回溯、位运算、动态规划

2.做题思路

这一题参考了左程云的leetcode网课,主要运用位运算的思路。考虑N皇后问时,我们可以在放置每个皇后的时候,按照行的顺序来放,也就是说如果把皇后从0到n-1编号的话,第i行放置的就是第i个皇后。使用位运算主要就是在选择第i行的位置时,将对这一行的限制存储在一个二进制表示的整数中,让0和1分别表示能放和不能放。这个总体限制的计算分别通过三个不同的限制来进行运算,在每一次寻找解的过程中去更新这个限制。位运算指的就是用这个二进制整数表示皇后被放置的限制和状态。

以八皇后问题为例子,我们使用一个2×82\times8的二进制数limit=0000 0000 1111 1111,这个limit是一个工具,其中的1表示能够被放的格子,0表示不能被放的。(也就是说,所有的尝试只会在右侧的N位中产生)。当我们在放置皇后时,皇后的位置和三个限制有关,之前的皇后占据的列以及它们的左斜线、右斜线都不能放。因此我们可以使用和limit相同大小的三个限制:

  • 列限制colLim: 0000 0000 0000 0000
  • 左斜线限制lLim: 0000 0000 0000 0000
  • 右斜线限制rLim: 0000 0000 0000 0000 假设某次尝试时,在第0行第0列放了一个皇后,那么这三个限制将会被更新为:
  • colLim: 0000 0000 1000 0000
  • lLim: 0000 0001 0000 0000
  • rLim: 0000 0000 0100 0000 分别表示当前的皇后对下一行决策产生的三个限制。我们需要根据这三个限制生成下一个决策的总限制,这个限制(avaPos)满足1只在最右侧8位,并且1表示下一个皇后能够选择的位置,计算方法如下: avaPos = limit & ~(colLim | lLim | rLim);

如果用上面的表达式来进行计算,avaPos: 0000 0000 0011 1111 ,也就是说最右侧的六位都可以放置下一个皇后。

递归的过程的变量除了四个限制int limit, int lLim, int rLim, int colLim,还需要一个存储每一行决策的vector<string> &pos,以及存储最终结果的vector<vector<string>> &result。整体的过程围绕avaPos展开,当avaPos不为0时,也就是说还有可以选择的情况下时,执行while循环,while循环里需要提取出最右侧的mostRight二进制数,并且不断地让avaPos减去mostRight以达到一次次尝试的效果。

在递归过程中,可以先把尝试的结果path存入pos中,在用更新后的限制值去递归,然后在递归结束后重新恢复现场。当pos的长度等于N时,存入res中。在递归过程中还需要对限制进行更新,limit在递归过程中永远不变,colLim需要或上当前尝试的位mostRight,也就是colLim | mostRight,lLim需要先或当前尝试的位,然后再左移一位,(lLim | mostRight) << 1,这样就能够将对角线传递下去, rLim需要先或当前尝试位再右移,与lLim类似,但是需要注意右移时要保证无符号右移,因此要加一个类型转换再右移,也就是(unsigned int)(rLim | mostRight) >> 1

三、AC 代码:

class Solution
{

public:
    void pos2string(int n, int pos, string &s )
    {
        //1-> ".Q.."
        for (int j = 0; j < n; j++)
            s += '.';
        int index = (int)log2(pos);
        s[index] = 'Q';
    }
    void reculSolution(int n, int limit, int lLim, int rLim, int colLim,
                       vector<string> &pos, vector<vector<string>> &result)
    {

        int avaPos = limit & ~(colLim | lLim | rLim);
        int mostRight;
        string path="";
        while (avaPos != 0)
        {
            mostRight = avaPos & (~avaPos + 1);
            avaPos = avaPos - mostRight;
            pos2string(n, mostRight, path);
            pos.push_back(path);
            path="";
            reculSolution(n, limit, (lLim | mostRight) << 1, (unsigned int)(rLim | mostRight) >> 1,
                          colLim | mostRight, pos, result);
            if (pos.size() == n)
                result.push_back(pos);
            pos.pop_back();
        }
    }

    vector<vector<string>> solveNQueens(int n)
    {
        int limit = n == 32 ? -1 : (1 << n) - 1;
        int lLim = 0, rLim = 0, colLim = 0; //左对角线限制、右对角线限制、列限制
        vector<vector<string>> result;
        vector<string> pos;
        reculSolution(n, limit, lLim, rLim, colLim, pos, result);

        return result;
    }
};

四、总结:

1.这一题是一道典型的能够用位运算的题目,针对位运算能整理出相关的一些知识点:

1)针对只需要某一个范围的值,我们可以设置一个值,其中需要的范围内为1,不需要的为0,然后使用&运算就可以限制取值的范围。

2)从右到左遍历一个二进制数的每一位:

while(avaPos!=0){
	mostRight = avaPos & (~avaPos + 1); //取出最右位
    avaPos = avaPos - mostRight; //更新原数
}

2.N皇后的变形

leetcode 52:这一题也是N皇后问题,但是需要求放置的种数,具体解法见:leetcode 52.N-Queens II【递归、位运算】【hard】


本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情