[Leetcode題解]605. 種花問題-貪心算法+衛語句重構

一. 先看一下題目

假設你有一個很長的花壇,一部分地塊種植了花,另一部分卻沒有。可是,花卉不能種植在相鄰的地塊上,它們會爭奪水源,兩者都會死去。

給定一個花壇(表示爲一個數組包含0和1,其中0表示沒種植花,1表示種植了花),和一個數 n 。能否在不打破種植規則的情況下種入 n 朵花?能則返回True,不能則返回False。

 

 

 

 

重點理解: 不能連續種花, 要預留空位, 所以實際是3個連續空位只能中間種一個。

 


二. 解題思路+代碼

咋眼一看, 好像要dp才能解決?實際上連續多個空位的時候, 遇到偶數個位置且右邊沒有種植都是可以直接種花的,可以用歸納法推導一下。

  • 貪心算法, 識別出連續的三個空位, 則在中間位置種花, 詳見代碼註釋吧。

需要注意細節處理:左右邊界考慮成空位即可;  邊界條件比較多, 三個位置都要考慮,則以中間位置爲中心進行遍歷即可,只是代碼複雜度要處理好;

 1 // canPlaceFlowers1 核心使用貪心算法, 識別到連續3個空位時在中間種花;
 2 // occupied記錄上一個位置是否已種花, 遍歷當前位置爲空位時, 同時檢查右邊是否爲空位;
 3 func canPlaceFlowers1(flowerbed []int, n int) bool {
 4     plant := 0        // 記錄當前可以新種植的數量
 5     occupied := false // 初始化時, 左邊界認爲沒有被佔用
 6     for i := 0; i < len(flowerbed); i++ {
 7         if !occupied && flowerbed[i] == 0 {
 8             // 當前有空位, 且右邊有空位則貪心算法在該位置種植
 9             if i+1 < len(flowerbed) {
10                 // 下一個位置可以種則才需要刷新occupied標記, 否則保持爲false
11                 if flowerbed[i+1] != 1 {
12                     plant++
13                     if plant >= n {
14                         return true
15                     }
16                     occupied = true
17                 }
18             } else {
19                 // 已到尾部, 不用判定右邊界
20                 plant++
21                 return plant >= n
22             }
23         } else {
24             // 注意以當前位置的種植情況進行更新
25             occupied = flowerbed[i] == 1
26         }
27     }
28     return plant >= n
29 }

 

三. 重構一下+代碼

上面的代碼是看到題目理了一下思路後直接就開始寫的, 最深處縮進了5層,複雜度直線拉昇, 還容易邊界處理錯誤 :) 靈機一動, 衛語句剛好適用於這種問題的重構呢;

 1 // canPlaceFlowers 使用貪心算法實現, 在上面直觀解法上進行了邏輯梳理
 2 // 重構: 以衛語句取代嵌套條件表達式(Replace Nested Conditional with Guard Clause)
 3 func canPlaceFlowers(flowerbed []int, n int) bool {
 4     plant := 0        // 記錄當前可以新種植的數量
 5     occupied := false // 初始化時, 左邊界認爲沒有被佔用
 6     for i := 0; i < len(flowerbed); i++ {
 7         // 先判定當前位置是否被佔用
 8         if flowerbed[i] == 1 {
 9             occupied = true
10             continue
11         }
12 
13         // 在判定上一個位置是否被佔用, 已被佔時當前位置不能種, 直接刷新狀態後跳過當前位置;
14         if occupied {
15             occupied = false
16             continue
17         }
18 
19         // 檢查是否已經到了末尾, 右邊界認爲未被佔用不用判定直接種
20         if i+1 >= len(flowerbed) {
21             return plant+1 >= n
22         }
23 
24         // 未到末尾, 則檢查右邊是否有空位, 有的話直接種植
25         if flowerbed[i+1] == 0 {
26             plant++
27             if plant >= n {
28                 return true
29             }
30             occupied = true
31         }
32     }
33     return plant >= n
34 }

 

重構手法複習:

 簡化條件表達式之以衛語句取代嵌套條件表達式(Replace Nested Conditional With Guard Clauses)

函數中的條件邏輯使人難以看清正常的執行途徑。使用衛語句表現所有特殊情況。

動機:條件表達式通常有2種表現形式。

第一:所有分支都屬於正常行爲。

第二:條件表達式提供的答案中只有一種是正常行爲,其他都是不常見的情況。

       這2類條件表達式有不同的用途。如果2條分支都是正常行爲,就應該使用形如if…..else…..的條件表達式;如果某個條件極其罕見,就應該單獨檢查該條件,並在該條件爲真時立刻從函數中返回。這樣的單獨檢查常常被稱爲“衛語句”。

       Replace Nested Conditional with Guard Clauses (以衛語句取代嵌套條件表達式)的精髓是:給某個分支以特別的重視。它告訴閱讀者:這種情況很罕見,如果它真的發生了,請做一些必要的整理工作,然後退出。

       “每個函數只能有一個入口和一個出口”的觀念,根深蒂固於某些程序員的腦海裏。現今的編程語言都會強制保證每個函數只有一個入口,至於“單一出口”規則,其實不是那麼有用。保持代碼清晰纔是最關鍵的:如果單一出口能使這個函數更清晰易讀,那麼就使用單一出口;否則就不必這麼做。

做法:1、對於每個檢查,放進一個衛語句。衛語句要不就從函數返回,要不就拋出一個異常。

       2、每次將條件檢查替換成衛語句後,編譯並測試。如果所有衛語句都導致相同的結果,請使用 Consolidate Conditional Expression (合併條件表達式)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章