一、题目描述:
leetcode 51.N-Queens: n皇后问题,要求返回指定格式的二维列表,其中“Q”表示皇后的位置,“.”表示皇后不在的位置。
二、思路分析:
1.类别
递归、回溯、位运算、动态规划
2.做题思路
这一题参考了左程云的leetcode网课,主要运用位运算的思路。考虑N皇后问时,我们可以在放置每个皇后的时候,按照行的顺序来放,也就是说如果把皇后从0到n-1编号的话,第i行放置的就是第i个皇后。使用位运算主要就是在选择第i行的位置时,将对这一行的限制存储在一个二进制表示的整数中,让0和1分别表示能放和不能放。这个总体限制的计算分别通过三个不同的限制来进行运算,在每一次寻找解的过程中去更新这个限制。位运算指的就是用这个二进制整数表示皇后被放置的限制和状态。
以八皇后问题为例子,我们使用一个的二进制数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 春招闯关活动」, 点击查看 活动详情