友情链接:飘渺阁、Ubuntu修炼地

[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 (合并条件表达式)。

 

posted @ 2021-01-02 00:07  Neo Nengrong Qu  阅读(279)  评论(0编辑  收藏  举报
我思故我在、身在尘嚣中、思飞九天外 ...