1. 程式人生 > >【Codewars】7×7 摩天大樓

【Codewars】7×7 摩天大樓

註意 排序 沖突 one 目標 pla 效果 成功 rap

技術分享圖片

介紹

鏈接:7×7 Skyscrapers

C#答案(原因:懶,但是完全可以轉成C++):bajdcc/learnstl

題目(機翻):

在7乘7格的網格中,你只想在每個廣場上放置一個摩天大樓,只有一些線索:

  • 摩天大樓的高度在1到7之間
  • 一行或一列中的所有摩天大樓樓層數量各不相同
  • 你只知道從外面的一行或一列中看到的摩天大樓的數量
  • 高層摩天大樓阻擋了後面的下層摩天大樓,導致你看不到後面的大樓了

你能寫一個能解決這個難題的程序嗎?

舉個例子,6x6的:

技術分享圖片

6x6 摩天大樓 例

(有人說看不懂規則3和4,我就以圖來說明一下)

我根據上圖制作了一個假城市(bajdcc/UnityTest),如下:

技術分享圖片虛擬城市

我們看線索中的左側(3,4,4):

技術分享圖片規則3和4的說明

要求

輸入:int[]

技術分享圖片

輸入的數組下標

輸出:int[][]

分析

花了兩天思考這個系列的問題,最終當然是寫出來了(光想沒用)。

這個題目感覺跟數獨很像,我最先想的思路就是一步步推理,直到解決問題,然而,4x4大小的推理規則在6x6大小的問題中,可能需要補充一些東西或有些東西不實用了,這就麻煩了。

老實說,做數獨題的時候,我不怎麽用窮舉法,我是對當前局面進行推理,利用排除法一步步確定答案,但用代碼實現推理就變得困難了,雖然這樣做是最優的(因為根據不做多余計算)。
推理不能用,那就老老實實用窮舉法吧,雖然窮舉法有點low,但只要能過就行。

題目中透露了一些可供優化的信息:各行各列中數是唯一的。這就意味著復雜度從O(n^n)下降到O(n!)。這跟八皇後有點像,但又有所不同,因此我就用回溯法做。

進一步思考

確定用回溯做,也感覺可以借鑒八皇後的思路,還不能馬上開工,有幾個問題:

  1. 全排列的生成,這個嘛拿之前的代碼粘貼下
  2. 回溯遍歷的順序,這個順序大有文章

先解決全排列問題:抄了下https://github.com/bajdcc/jProlog/blob/master/src/com/bajdcc/rt/gen/array/RtNorepArray.java#L43 哈哈。

回溯的順序應該是怎樣呢?自己試嘍。

先生成全排列,然後計算從左邊向右看到的樓層數(假設為Sky函數,Sky:=int->int),將結果存到字典中。

會發現,當n=7時,sky(7)=1,可能性最低,都算出來後,排序。

sky(7)<sky(6)<sky(5)<sky(1)<sky(4)<sky(3)<sky(2)

展開順序的話,我肯定先找n=7的情況的,因為這時的不確定性最低(解只有一個),相當於可以確定一個格子的值了。

目前的思路是:找到值=7的數,就直接敲定一排解(1~7);再找值=6的數,這時答案不唯一,回溯;繼續找……

這樣運行程序的話,肯定是耗時很長的,原因是什麽?

再想一想

我們優化程序的目標是回溯時盡可以減少回溯次數,那就必須在決定一個展開順序:先展開不確定性小的,再展開不確定性較大的。

有一種情況,可以減少不確定性:如果一排格子的兩側都有數字(都不是零),那其實可以將它們放到一起回溯。例: 5 | x x x x x x x | 2,這樣可以極大降低可能性。事實證明這樣做有一點效果。

思路有了:

  1. 將規則排好序(數大,對位匹配優先),回溯規則
  2. 保存現場,查看是否沖突,一是當前的測試值是否與該行/列沖突,二是測試值是否滿足各行/列數唯一的原則
  3. 如果失敗,回溯
  4. 如果成功,level+=1,再測試下一個規則
  5. 如果level==規則總數,意味著通過了所有規則,此時還不能確定解是否正確
  6. 規則有覆蓋不到的行/列,此時要將值是零的格子進行填空
  7. 填空的辦法是用排除法,然後不斷遍歷填空,如果最後填空失敗,則解不正確,如果填空成功,則解正確

註意點:

  1. 坐標系問題,方向問題
  2. 判斷條件是否完備
  3. 盡量用數組做(用list<>竟然比int[]快。。)
  4. 等等細節問題

代碼比較啰嗦,460行,不貼了(別人寫得都很短簡直了)。

由https://zhuanlan.zhihu.com/p/30713476備份。

【Codewars】7×7 摩天大樓