leetcode:N-Queens II 的位運算非遞歸解法

呼,第一篇博文,寫得不好歡迎扔白菜^_^

題目如下:

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了,歡迎大家參考及吐槽哈~



發佈了23 篇原創文章 · 獲贊 39 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章