一. 先看一下題目
假設你有一個很長的花壇,一部分地塊種植了花,另一部分卻沒有。可是,花卉不能種植在相鄰的地塊上,它們會爭奪水源,兩者都會死去。
給定一個花壇(表示爲一個數組包含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 (合併條件表達式)。