無心中看到這麼一道題,以爲頗有意思,題目以下:javascript
有三個和尚和三個妖怪要利用惟一的一條小船過河,這條小船一次只能載兩我的,同時,不管是在河的兩岸仍是在船上,只要妖怪的數量大於和尚的數量,妖怪們就會將和尚吃掉。如今須要選擇一種過河的安排,保證和尚和妖怪都能過河且和尚不能被妖怪吃掉。java
看完題目,首先想到的是暴力搜索。不斷地窮舉下一步的可能性,直到最終達成目標。由於搜索過程當中可能會有重複的狀態,因此須要對狀態進行哈希。git
如何表示當前的狀態?首先想到的是用多維數組進行哈希。咱們能夠用一個四維數組(其實徹底能夠用二維,左邊僧人妖怪的數量肯定後,相應地就能計算右邊了,須要多一步運算),假設左岸僧人和妖怪數量分別是 a 和 b,右岸僧人妖怪數量分別是 c 和 d,那麼咱們能夠用 [a][b][c][d]=true
表示這種狀況,也就是該狀態已經被搜索過了。這樣作還有個漏洞,船在左右兩邊是不一樣的狀況,因此還須要一個維度來表示船的位置,那麼能夠這樣,增長一維,用 [a][b][c][d][1]=true
表示船在左邊的狀況,用 [a][b][c][d][0]=true
表示船在右邊的狀況。這樣來表示狀態是徹底能夠的,可是衆所周知 JavaScript 下表示多維數組很是的麻煩,因此進一步思考能不能將狀態壓縮。github
繼續看,最開始時的狀態,如上能夠表示爲 [3][3][0][0][1]
,以後的搜索過程當中,狀態中的數字不可能大於 3,也就是數字的範圍在 0~3 中,這不是赤裸裸的四進制數嗎?因而咱們能夠將該狀態壓縮到一個四進制數 330001
中,可是四進制畢竟操做起來不大方便,能不能轉爲二進制?答案很明顯,一個四進制數能夠拆成兩個二進制,這樣就好辦了,將四進制的 33001
能夠轉成二進制 1111000001
,二進制的各類運算就方便多了!數組
考慮到最後一個維度的特殊狀況,最終我決定將前四個維度用一個二進制來處理,第五個維度(船的位置)單獨處理,用一個二維數組進行狀態哈希。安全
很顯然,咱們須要能將數組和二進制數互換的函數。函數
簡單寫了兩個互換函數,將數組轉爲數字的。好比將 [3, 3, 0, 0]
轉爲二進制大小爲 11110000
的數字:code
// array to number function aton(a) { var sum = 0; for (var i = a.length; i--; ) { var index = 3 - i , item = a[i]; (item & 1) && (sum |= (1 << (index << 1))); (item & 2) && (sum |= (1 << ((index << 1) | 1))); } return sum; }
將數字轉爲數組的,爲以上函數的逆運算:htm
// number to array function ntoa(n) { var a = []; for (var i = 0; i < 4; i++) { var num = 0 , index = 3 - i; num |= n & (1 << (index << 1)) ? 1 : 0; num |= n & ((1 << ((index << 1) | 1))) ? 2 : 0; a.push(num); } return a; }
接下去就很是簡單了,進行深度優先搜索,每次搜索枚舉下一個可能的狀態,對該狀態進行哈希,並把該狀態存入答案數組中,枚舉完進行回溯。ip
// pos == 1 表示船在左邊 // pos == 0 表示船在右邊 function dfs(num, pos) { if (hash[num][pos]) return; hash[num][pos] = true; var a = ntoa(num); var l_sNum = a[0]; var l_yNum = a[1]; var r_sNum = a[2]; var r_yNum = a[3]; pos ? a.push('left') : a.push('right'); ans.push(a); if (num === 15) { // [0, 0, 3, 3] // 打印答案 ans.concat().forEach(function(item) { console.log(item.toString() + '->'); }); console.log('------------------'); // backtrace hash[num][pos] = false; ans.pop(); return; } // left to right if (pos) { for (var i = 0; i <= l_yNum; i++) // 妖怪過河數 for (var j = 0; j <= l_sNum; j++) { // 僧人過河數 if (i + j === 0) continue; // 船上是否安全 if (!checkIfSafe(j, i)) continue; // 左岸是否安全 if (!checkIfSafe(l_sNum - j, l_yNum - i)) continue; // 右岸是否安全 if (!checkIfSafe(r_sNum + j, r_yNum + i)) continue; if (i + j > 2) break; // 過河後的數據 var b = [l_sNum - j, l_yNum - i, r_sNum + j, r_yNum + i]; dfs(aton(b), 0); } } else { // right to left for (var i = 0; i <= r_yNum; i++) for (var j = 0; j <= r_sNum; j++) { if (i + j === 0) continue; if (!checkIfSafe(j, i)) continue; if (!checkIfSafe(r_sNum - j, r_yNum - i)) continue; if (!checkIfSafe(l_sNum + j, l_yNum + i)) continue; if (i + j > 2) break; // 過河後的數據 var b = [l_sNum + j, l_yNum + i, r_sNum - j, r_yNum - i]; dfs(aton(b), 1); } } // backtrace hash[num][pos] = false; ans.pop(); }
簡單地看下深度優先搜索的函數,每次根據船所在的位置,枚舉下個狀態值。這裏我寫了個 checkIfSafe 函數來判斷當前數量的僧人和妖怪在一塊兒,會不會有危險。函數很是簡單:
function checkIfSafe(sNum, yNum) { return sNum === 0 || sNum >= yNum; }
最後的最後,解法有四種,大概是這個樣子:
3,3,0,0,left-> 2,2,1,1,right-> 3,2,0,1,left-> 3,0,0,3,right-> 3,1,0,2,left-> 1,1,2,2,right-> 2,2,1,1,left-> 0,2,3,1,right-> 0,3,3,0,left-> 0,1,3,2,right-> 1,1,2,2,left-> 0,0,3,3,right-> ------------------ 3,3,0,0,left-> 2,2,1,1,right-> 3,2,0,1,left-> 3,0,0,3,right-> 3,1,0,2,left-> 1,1,2,2,right-> 2,2,1,1,left-> 0,2,3,1,right-> 0,3,3,0,left-> 0,1,3,2,right-> 0,2,3,1,left-> 0,0,3,3,right-> ------------------ 3,3,0,0,left-> 3,1,0,2,right-> 3,2,0,1,left-> 3,0,0,3,right-> 3,1,0,2,left-> 1,1,2,2,right-> 2,2,1,1,left-> 0,2,3,1,right-> 0,3,3,0,left-> 0,1,3,2,right-> 1,1,2,2,left-> 0,0,3,3,right-> ------------------ 3,3,0,0,left-> 3,1,0,2,right-> 3,2,0,1,left-> 3,0,0,3,right-> 3,1,0,2,left-> 1,1,2,2,right-> 2,2,1,1,left-> 0,2,3,1,right-> 0,3,3,0,left-> 0,1,3,2,right-> 0,2,3,1,left-> 0,0,3,3,right-> ------------------
完整代碼點 這裏。