[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 (合并条件表达式)。
作者:逸云沙鸥
出处:http://www.cnblogs.com/QuLory/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。