呼,第一篇博文,寫得不好歡迎扔白菜^_^
題目如下:
Follow up for N-Queens problem.
Now, instead outputting board configurations, return the total number of distinct solutions .
這是經典的N皇后問題,就是在一個N*N的國際象棋棋盤上放置N個皇后,使得任意一個皇后不能吃到別的皇后,按國際象棋的規則,皇后可以吃與它在同一行、同一列或者對角線上的棋子,上面是題目中給出的一種可行擺法,其中對於每一個皇后,它所在的行、列以及對角線上都沒有別的皇后。自然能想到,當在一行中的某列放置一個皇后時,去檢查之前放的皇后能不能吃到它,如果能吃到,則不能擺,於是在該行中的下一列進行試探,如果能擺放,則擺放,然後繼續到下一行,如果一行中所有列都不能擺放,就回退到上行一,把上一行的皇后移到下一列再進入該行進行擺放,這是一個遞歸的過程,其中的關鍵在於檢查之前放的皇后能不能吃到它。目前最高效的判斷方法是用位運算來判斷,大家可以參考http://blog.csdn.net/xadillax/article/details/6512318。本文的重點不在於講解如何使用位運算求解該問題,而是給出用位運算求解該問題的一個非遞歸版本,因爲我在網上找到的用位運算求解該問題的都是遞歸做的,相信已經有大牛實現過位運算求解該問題的非遞歸版本,也許是沒貼出來而已~
好~在給出用位運算求解該問題的一個非遞歸版本,先給出我的一個遞歸的版本,方便大家對照着看,其中的詳細原理請參考上面給出的鏈接:
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了,歡迎大家參考及吐槽哈~