zoukankan      html  css  js  c++  java
  • [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 (合并条件表达式)。

  • 相关阅读:
    Java内部类与异常类
    Java 继承和接口
    134. 加油站
    P1567 统计天数
    P2141 珠心算测验
    P1428 小鱼比可爱
    P1427 小鱼的数字游戏
    python中使用xlrd、xlwt操作excel表格详解
    同步机制
    CSS学习
  • 原文地址:https://www.cnblogs.com/QuLory/p/14221353.html
Copyright © 2011-2022 走看看