您的位置:首页 > 产品设计 > UI/UE

leetcode:N-Queens II 的位运算非递归解法

2014-06-28 14:47 302 查看
呼,第一篇博文,写得不好欢迎扔白菜^_^

题目如下:

Follow up for N-Queens problem.

Now, instead outputting board configurations, return the total number of distinct solutions .



这是经典的N皇后问题,就是在一个N*N的国际象棋棋盘上放置N个皇后,使得任意一个皇后不能吃到别的皇后,按国际象棋的规则,皇后可以吃与它在同一行、同一列或者对角线上的棋子,上面是题目中给出的一种可行摆法,其中对于每一个皇后,它所在的行、列以及对角线上都没有别的皇后。自然能想到,当在一行中的某列放置一个皇后时,去检查之前放的皇后能不能吃到它,如果能吃到,则不能摆,于是在该行中的下一列进行试探,如果能摆放,则摆放,然后继续到下一行,如果一行中所有列都不能摆放,就回退到上行一,把上一行的皇后移到下一列再进入该行进行摆放,这是一个递归的过程,其中的关键在于检查之前放的皇后能不能吃到它。目前最高效的判断方法是用位运算来判断,大家可以参考/article/10225093.html本文的重点不在于讲解如何使用位运算求解该问题,而是给出用位运算求解该问题的一个非递归版本,因为我在网上找到的用位运算求解该问题的都是递归做的,相信已经有大牛实现过位运算求解该问题的非递归版本,也许是没贴出来而已~

好~在给出用位运算求解该问题的一个非递归版本,先给出我的一个递归的版本,方便大家对照着看,其中的详细原理请参考上面给出的链接:

int totalNQueens(int n) {
//ans:记录找到的解的个数。
//finished:用n位全1表示行已经放好,这是显然的,
//因为每行都可以放一个,关键是放在该行的哪一列
long ans=0,finished=1;
//将finished变成n位全1
finished=(finished<<n)-1;
func(0,0,0,ans,finished);
return ans;
}
void func(int row,int leftDiag,int rightDiag,long &ans,long finished){
//row:当前哪一列已经摆好了,那一列对应的位就是1,否则是0
/*leftDiag:对当前行,之前已经摆好的皇后中,由于从右上角到左下角的对角线的影响(之前的皇后可以通过这个对角线吃掉要摆的皇后),
造成当前行的哪一列不能摆,那一列对应的位就是1,否则是0*/
/*rightDiag:和leftDiag类似,对当前行,之前已经摆好的皇后中,由于从左上角到右下角的对角线的影响,
造成当前行的哪一列不能摆,那一列对应的位就是1,否则是0*/
if(row==finished){
//当row==finished,即row的n位全是1,
//就是说每一列都摆好了,则找到了一个解
ans++;
}else{
//canPlace:当前行的哪一列可以摆,那一列对应的位就是1,否则是0
long canPlace=finished&(~(row|leftDiag|rightDiag));
while(canPlace){//如果至少一列可以摆
long pos=canPlace&(-canPlace);//取出最右边的那一列
canPlace-=pos;//把该列从能摆的列中去掉
func(row|pos,(leftDiag|pos)<<1,(rightDiag|pos)>>1,ans,finished);//递归进入下一行
}
}
}


下面讨论非递归的版本,要把递归转化成非递归,就必须先明白递归的工作原理,其中的关键是递归工作栈,大家看看上面的func函数,当它在自己的函数体中再调用自己的时候,那么下一次进入func时,它是不是修改了里面的变量?那么但一次调用结束后,函数退出了,回到上一层,这些变量不是被修改了么?如何找回修改之前的值?原来函数在每次调用自己前,将变量压入递归工作栈中,那么在一次进入func时,里面的变量就随它爱怎么修改就怎么修改,反正我已经保存了之前的变量值了。一次调用结束后,回到上一层,就把刚才保存的变量弹出栈,恢复调用前的值,明白了这些原理,就可以自己用栈去模拟递归啦~下面给出我的非递归版本,其中的变量含义与递归版本中的同名变量相同:

int totalNQueens(int n) {
//下面注释中提到的func函数是指递归版本中的func函数
long ans=0,finished=1,row=0,leftDiag=0,rightDiag=0,canPlace=1;
finished=(finished<<n)-1;
stack<long> myStack;//用栈模拟递归压栈
/*大家想想调用func函数的时候,哪些变量需要保存(即压栈)?
是row,leftDiag,rightDiag,canPlace。为什么呢?因为递归进入下一次的func函数时,
里面的row,leftDiag,rightDiag,canPlace要被修改,如果不保存,那么本次func调用结束后,
就找不到原来的row,leftDiag,rightDiag,canPlace啦*/
myStack.push(row);
myStack.push(leftDiag);
myStack.push(rightDiag);
canPlace=finished&(~(row|leftDiag|rightDiag));
myStack.push(canPlace);
while(!myStack.empty()){
//按压栈相反的顺序弹出栈,这个不用解释了吧?
canPlace=myStack.top();
myStack.pop();
rightDiag=myStack.top();
myStack.pop();
leftDiag=myStack.top();
myStack.pop();
row=myStack.top();
myStack.pop();
//下面就是func中的while循环
while(canPlace){
long pos=canPlace&(-canPlace);
canPlace-=pos;
//大家看func中是不是算完canPlace-=pos之后就要下一次调用func了?所以马上压栈~
myStack.push(row);
myStack.push(leftDiag);
myStack.push(rightDiag);
myStack.push(canPlace);
row=row|pos;
if(row==finished)
ans++;
leftDiag=(leftDiag|pos)<<1;
rightDiag=(rightDiag|pos)>>1;
canPlace=finished&(~(row|leftDiag|rightDiag));
}
}
return ans;
}


以上两个版本代码均在leetcode中AC了,欢迎大家参考及吐槽哈~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐