LeetCode 605种花问题详解:用贪心算法轻松搞定边界条件
发布时间:2026/10/13 22:29:37 锦皓数字建站

1. 先把题目当成生活中的规则来读1.1 这道题到底在问什么LeetCode 605“种花问题”光看名字你可能觉得是农业题其实它是一道很典型的贪心算法入门题。我当年在某大厂机考模拟卷里第一次碰到它第一反应是“这不就是小学奥数的间隔植树问题吗”但真上手一写才发现边界条件能把人绕晕。题目背景非常生活化你家有一个花坛花坛是一排连续的坑位每个坑位要么已经种了花用 1 表示要么空着用 0 表示。规则是花不能种在相邻的坑位里因为花长大了会互相遮挡阳光。现在你手里有 n 颗花苗问能不能在不违反规则的前提下把这些花苗全部种下去。用程序的语言翻译一遍给定一个包含 0 和 1 的数组 flowerbed再给定一个整数 n判断是否还能种下 n 朵花。相邻的两个位置不能同时为 1这是硬性条件。这道题在很多公司的笔试里属于“热身题”和“筛选题”之间的一道分水岭。热身题是那种大家都会写的字符串反转筛选题是那种一眼看上去简单、但下手全是坑的动态规划。605 就卡在中间说难吧它没有复杂数据结构说简单吧你要是没理清边界分分钟写出一堆 if 嵌套最后自己都看不懂。1.2 关键条件其实就三条新手读题最容易犯的错是把简单规则想复杂。我带着很多基础薄弱的朋友刷过这道题总结下来题目的有效信息只有三条花坛里的坑位只有两种状态空0和已种花1相邻两个坑位不能同时种花即不能出现连续的两个 1花坛初始状态已经固定你只能在空的坑位里加花不能拔掉已有的花重新安排尤其要注意第三条。很多人第一反应是“我先把所有空位统计出来然后隔一个种一个”这个思路在空花坛全 0时成立但一旦数组里有零星分布的花直接套用“隔一个种一个”就会误判。比如 [0, 1, 0, 0, 1, 0] 这个数组看起来空位有 4 个但实际能再种的花只有 0 朵因为每个空位都被已有的花“物理隔离”了。搞清楚这三条规则之后难题就变成了一个纯模拟问题从左到右扫描花坛遇到能种的位置就种统计种下去的数量最后看够不够 n。2. 官方题解的“弯”到底弯在哪2.1 官方分段统计法的问题这道题在力扣上的官方题解以及很多大神的题解普遍采用“分段统计连续 0 的个数”的方法。思路是把数组按已有的 1 切分成若干段每段连续 0 的长度为 count然后套一个公式去计算该段最多能种多少花。公式大致长这样如果这段在数组中间左右都被 1 夹住能种的数量是(count - 1) / 2如果这段贴着数组左边界能种的数量是count / 2如果这段贴着数组右边界能种的数量也是count / 2我第一次看这个解法时整个人是懵的。凭什么中间要减 1除法怎么取整如果连续 0 有 4 个为什么中间段只能种 1 朵而不是 2 朵每一个问号背后都是一个边界场景光靠背公式很难在考场上快速写对。更难受的是这种解法对基础的初学者不友好。它把问题抽象成了“数数 套公式”如果公式记混了代码就彻底崩了。我在教别人的时候发现十个新手看官方题解至少有六个人会问“为什么右边界的 count / 2 和中间段不一样”。不是他们笨而是这个解法确实跳过了直观逻辑直接蹦到了数学结论。2.2 分段统计法为什么容易写错我们手动推一遍官方思路你就知道坑在哪了。假设数组是 [1, 0, 0, 0, 0, 1]中间连续 4 个 0左右都被 1 挡住。你用手比划一下第 2 个位置从左往右数可以种花种了之后第 3 个位置就不能种了第 4 个位置可以种第 5 个位置又不能种。所以最多能种 2 朵。公式(count - 1) / 2在这里是(4 - 1) / 2 3 / 2 1但实际能种 2 朵公式算少了。问题出在哪官方题解里的 count 指的是“被两个 1 夹住的纯连续 0 段”但在首尾位置时边界条件的处理方式不同要单独判断。而且不同的题解对“边界”的定义还不完全一样有的人把数组外也想象成一个 1有的人想象成一个 0想象方式不同公式就不同。这就导致一个很尴尬的局面初学者看懂了官方题解的原理但一写代码要么忘了对首尾特殊处理要么把公式的减一只加在中间段却忘了首尾段最终结果要么少算要么多算。面试时候本来时间就紧再在这种边界细节上翻车心态直接崩。所以我的建议很明确如果你想在机考里快速拿下这道题或者说你是基础薄弱型选手不要一上来就啃官方题解那套分段统计法。换一条更直白、更贴近生活直觉的路子。3. 新手最容易理解的核心思路能种就种3.1 贪心思想在这道题里的正确姿势“能种就种”这个策略专业术语叫贪心算法。贪心的核心就一句话每一步都做当下看起来最优的选择并且这个选择不会堵死后面的路。种花问题恰好满足贪心的适用条件。你可以把花坛想象成一排座位规则是两个人不能坐一起现在问你最多还能安排几个人入座。从最左边开始看到一个空位先瞄一眼左边和右边有没有人如果两边都没人立刻坐下去如果左边或右边有人就跳过这个位子继续往右看。为什么这是对的因为从左到右扫描时如果你在当前空位种花最多只会影响右边紧挨着的那个位置左边的位置已经扫描过不会再改变而如果你不种这个空位也不会对更右边的位置产生任何好处反而白白浪费了一个可用坑位。所以“能种就种”不但不会减少总容量反而是让总容量最大化的做法。对比一下官方的“分段统计”你会发现贪心方案几乎不需要记忆任何公式。你只需要记住三个字的动作能种就种。剩下的事情就是写代码判断“能不能种”。3.2 边界位置的统一处理逻辑“能种就种”看起来简单但真正写代码时第一个拦路虎就是数组越界。比如你现在站在下标为 0 的位置你想检查左边有没有花一访问 i - 1 就变成 -1直接报错站在最后一个位置访问 i 1 又会越界。这里有个很好用的技巧把数组想象成无限延伸的假想花坛在真正数组的左右两边各补上一个 0即假想坑位已经有花。这样判断每个位置时都统一检查 left 和 right不用再区分什么“首位置只看右”“尾位置只看左”。但新手直接写“假想补位”又觉得抽象。我建议用更直接的写法先写死一个检查函数canPlace专门判断某个位置能不能种花。在这个函数里把“越界”当成“有花”来处理。什么意思呢如果一个位置越界了我们就认为它已经有花了即不能种。用一个三元表达式或者带短路逻辑的条件判断就能写出来。判断一个空位能不能种花需要同时满足三个条件当前这个位置的值必须是 0空位才能种左边的位置不存在或者左边的位置是 0右边的位置不存在或者右边的位置是 0注意这里的“不存在”就等价于“视为有花”因为越界位置不可能真的种花但它会阻断相邻性。这个逻辑用代码写出来非常干净不管扫到数组的哪个位置判断逻辑完全相同完全不需要区分首尾。我觉得这是比官方题解更适合新手的原因之一没有一堆分支没有语境切换。4. 对照代码逐行拆解多语言版本4.1 C 版本短小直观面试够用以下是我推荐给初学者的 C 写法。它不追求奇技淫巧就是老老实实扫描 判断。class Solution { public: bool canPlaceFlowers(vectorint flowerbed, int n) { int count 0; // 记录最多还能种下多少朵花 int m flowerbed.size(); for (int i 0; i m; i) { // 当前必须是空位 if (flowerbed[i] 0) { // 左边越界视为已有花所以用 true 跳过 bool leftEmpty (i 0) || (flowerbed[i - 1] 0); bool rightEmpty (i m - 1) || (flowerbed[i 1] 0); if (leftEmpty rightEmpty) { // 种花把当前位置改成 1避免影响后续判断 flowerbed[i] 1; count; // 提前结束已经够种了没必要继续扫描 if (count n) { return true; } } } } // 循环结束还没达到 n说明种不下 return count n; } };这个代码有几个细节新手需要注意。第一bool leftEmpty (i 0) || (flowerbed[i - 1] 0);这一行的||有短路作用当i 0为 true 时flowerbed[i - 1]根本不会被执行所以不会越界。同理(i m - 1) || (flowerbed[i 1] 0)在最后一个位置时也不会越界。这就是把“越界视为有花”的落地实现。第二我种完花后直接修改了原始数组flowerbed[i] 1。这样做的好处是后续位置判断时天然知道左边刚才种过花了不用再额外用一个布尔变量记住“我刚种过”。坏处是修改了传入的参数如果在面试里和面试官讨论最好说明一下这个副作用。对于机考笔试来说这个副作用无所谓函数结束后数组就没有利用价值了。第三提前终止的if (count n) return true;是个好习惯。如果 n 是 3而你扫描到一半已经种满 3 朵后面的位置根本不用看直接返回 true能省不少时间。4.2 Java 版本逻辑一致注意数组长度class Solution { public boolean canPlaceFlowers(int[] flowerbed, int n) { int count 0; int m flowerbed.length; for (int i 0; i m; i) { if (flowerbed[i] 0) { boolean leftEmpty (i 0) || (flowerbed[i - 1] 0); boolean rightEmpty (i m - 1) || (flowerbed[i 1] 0); if (leftEmpty rightEmpty) { flowerbed[i] 1; count; if (count n) { return true; } } } } return count n; } }Java 和 C 在逻辑上几乎一一对应。唯一提醒的是 Java 的||运算符同样是短路求值所以这段代码在边界位置不会抛出ArrayIndexOutOfBoundsException。很多初学者担心“我写 i - 1 会不会越界”其实只要把边界判断放在前面后面的访问就不会发生。4.3 Python 版本简洁但容易写出“优雅但错误”的代码class Solution: def canPlaceFlowers(self, flowerbed: List[int], n: int) - bool: count 0 m len(flowerbed) for i in range(m): if flowerbed[i] 0: left_empty (i 0) or (flowerbed[i - 1] 0) right_empty (i m - 1) or (flowerbed[i 1] 0) if left_empty and right_empty: flowerbed[i] 1 count 1 if count n: return True return count nPython 的or也是短路逻辑因此可以放心用。另外提醒一点Python 的列表支持负数索引如果你不把i 0放在前面当 i 等于 0 时访问flowerbed[-1]不会报错但会取到数组最后一个元素这在逻辑上是错的而且很难排查。所以边界判断千万别省也别自作聪明用负数索引去规避。三个语言版本核心逻辑完全一致这也是我推荐初学者的原因算法思想懂了任何语言都只是换一层皮。你不需要背某个语言的特定写法只要把“能种就种”的判断逻辑吃透考场上哪怕临时换语言也能写出来。5. 自己动手画图验证的完整过程5.1 手动模拟一个基础用例纸上谈兵没有意思我给你演示一个从头到尾的完整模拟过程你拿着笔照着画一遍绝对比看代码十遍都管用。假设flowerbed [1, 0, 0, 0, 1]n 2问你能否种下 2 朵花按照我们“能种就种”的策略从左往右扫描i 0值是 1已种花跳过i 1值是 0检查左边 flowerbed[0] 1有花所以不能种跳过i 2值是 0左边 flowerbed[1] 0右边 flowerbed[3] 0两边都空可以种。把位置 2 改为 1count 变成 1此时 count 1 2继续i 3值是 0左边 flowerbed[2] 1刚种的右边 flowerbed[4] 1两边都不满足不能种跳过i 4值是 1跳过循环结束count 1小于 2返回 false。这个用例非常经典。你注意看数组里一共有 3 个空位但能种的只有中间那一个。如果你用“隔一个种一个”的直觉很容易误判成能种 2 朵。这就是为什么必须老老实实做边界检查。5.2 再模拟一个容易误判的长数组再来一个更复杂的例子flowerbed [0, 0, 1, 0, 0, 0, 0, 1, 0]n 2。手动走一遍i 0值是 0左边越界视为有花右边 flowerbed[1] 0右边空。左边不空所以不能种。等等越界不是视为有花吗那左边不空就说明 i 0 不能种。幸好我们还有这个判断。i 1值是 0左边 flowerbed[0] 0右边 flowerbed[2] 1右边有花不能种i 2值是 1跳过i 3值是 0左边 flowerbed[2] 1不能种i 4值是 0左边 flowerbed[3] 0右边 flowerbed[5] 0可以种。位置 4 改为 1count 1i 5值是 0左边 flowerbed[4] 1刚种的不能种i 6值是 0左边 flowerbed[5] 0右边 flowerbed[7] 1右边有花不能种i 7值是 1跳过i 8值是 0左边 flowerbed[7] 1不能种循环结束count 1小于 2返回 false。这个例子很多人第一次做的时候会数出 2 朵因为空位特别多而且右下角那个位置感觉能种。但实际上位置 6 和位置 8 都被位置 7 的现有花隔离了真正能种只有位置 4。手动走完一遍你就知道为什么“数空位”这么不可靠。5.3 提前终止能省多少事还是上面那个例子如果 n 1那么扫描到 i 4 种下第一朵花后count 1 n直接返回 true后面 4 个位置根本不用再看了。这种提前终止在实际工程里意义不大但在机考笔试题里是个人都会紧张能少写几步逻辑就少几步Bug 出现的概率也会降低。6. 复杂度分析与为什么这题能“剪枝”6.1 时间复杂度和空间复杂度这段代码的时间复杂度很简单只需要遍历一次数组每个位置最多被访问常数次当前位置、它左右各一次因此时间复杂度是 O(n)n 是花坛长度。空间复杂度你只用了两个布尔变量和一个整数变量没有借助额外数组、哈希表、栈之类的结构所以空间复杂度是 O(1)。这两个复杂度指标在大厂机考里属于“标准解法”级别。很多面试官看到你写 O(1) 空间会觉得你至少不是一个只知道暴力破解的人。虽然没有动态规划那种让人眼前一亮的感觉但对笔试来说完全够用。6.2 提前终止能否改变最坏情况复杂度有人可能会问你加了提前终止时间复杂度是不是就变成 O(k) 了其实不会。如果 n 特别大比如 n 等于数组长度的一半甚至更大你需要遍历完整个数组才能得出结论所以最坏时间依然是 O(n)。提前终止只在“正好种满 n 朵但数组剩余部分仍然很长”时才有显著收益。比如前 20% 的长度里已经种满了后面 80% 都不用看。这种优化在题目里属于“锦上添花”不算核心考点。但我会把它写在代码里因为面试官偶尔会问“这个函数可以优化吗”你直接指着提前终止说“当达到目标数量后就不再扫描”能展示出性能敏感的意识。6.3 贪心解法与官方解法复杂度的对比官方“分段统计”解法的时间复杂度也是 O(n)空间复杂度是 O(1)。也就是说两种方案在理论效率上没有区别。既然效率一样选哪一个就完全看你对哪个更熟悉、更不容易写错。对基础薄弱的选手来说贪心解法理解成本低、边界统一、代码量还少显然是更合适的选择。7. 实战中容易踩的五个坑7.1 坑一只检查右边忘了左边我见过太多初学者写这道题时代码长这样if (flowerbed[i] 0 flowerbed[i 1] 0) { flowerbed[i] 1; count; }这种写法只看了右边没有花完全不看左边。在空数组[0, 0, 0]这种全零场景下碰巧能过但只要数组里有零星已有的花结果就错了。比如[1, 0, 0]你站在 i 1 时右边是 0按这个逻辑就能种可实际上左边已经有一朵花了根本不能种。所以必须养成习惯判断能不能种花左右两边都要看。每次多写一个条件权当给自己充了个保险。7.2 坑二觉得数组外也能种花有的同学想着“我能不能在数组边界外面种一朵这样首位置就不用考虑左边了”这是没有意义的。题目限定花坛数组就是全部空间数组外不存在坑位。咱们在代码里把越界当成有花只是为了让逻辑统一而不是真的认为边界外有花。这两个概念别混淆。7.3 坑三n 0 时的特殊处理漏掉当 n 0 时你手里没有花苗那答案必然是 true。有些写法在最前面加一行if (n 0) return true;这是正确的不过即便没有这一行我们的代码也能自然处理count 从 0 开始循环结束count n即0 0返回 true。所以“加不加特判”取决于你个人风格但新手容易在 n 0 时手动看代码觉得逻辑有问题实际上没毛病。7.4 坑四对空数组或长度为 1 的数组处理不当题目虽然规定了flowerbed至少包含一个元素但面试官或者你自己测试时可能会拿[0]或[1]这种极端用例来验证。对于[0]m 1i 0 时左边 i 0 为 true右边 i m - 1 为 true两边都空可以种 1 朵。对于[1]i 0 时当前值不是 0跳过count 0。这两个场景我们的代码都能正确处理但你心里最好有个数遇到极端用例不用慌。7.5 坑五拿“计数空位”来代替真正的条件判断这是最隐蔽的坑。有的同学背了官方公式后写出来的代码是统计所有空位数量再套公式结果在复杂场景下公式记忆错乱。比如[0, 0, 1, 0, 0]这种空位数是 4但实际能种多少左边两个空位中只能种 1 个右边两个空位中也只能种 1 个总共 2 个。公式好算但一旦数组变成[1, 0, 0, 0, 0, 1, 0, 0]这种有不对称边界的公式就很容易搞混。我们的贪心解法不存在任何公式条件写死了“左右都空才种”。哪怕你是对着代码抄抄完你也能用自己的话解释每一行在干嘛。8. 举一反三同类贪心题的面试扩展玩法8.1 从“种花”跳到“跳跃游戏”如果你能完全掌握“能种就种”的贪心思想那么恭喜你LeetCode 上另一类贪心题的门也打开了。最典型的就是“跳跃游戏”系列。跳跃游戏的问题是给你一个数组每个元素代表你在该位置最多能往前跳多远初始站在下标 0问能不能跳到最后一个位置。这类题的核心也是“每走到一个位置我都试图更新我能到达的最远距离”只要最远距离能覆盖到终点就返回 true。和种花问题一样它不需要回溯不需要全局枚举每一步做局部最优就能推出全局最优。我建议你把这两道题放在一起刷。先做 605 找感觉再做跳跃游戏体会“覆盖范围”这个概念的进化版你的贪心算法水平会有一个质的提升。8.2 “柠檬水找零”是另一个不错的练兵场LeetCode 860“柠檬水找零”也是贪心入门题。场景是你卖柠檬水一杯 5 美元顾客可能拿 5 美元、10 美元、20 美元来付账你需要判断能不能给每位顾客正确找零。贪心点在于收到 20 美元时优先用 10 美元 5 美元找零而不是三张 5 美元因为要尽量保留 5 美元这种“万能零钱”。这道题放在学习路径里能帮助你建立“局部选择会如何影响后续机会”的敏感度。你会发现种花问题里“能种就种”和找零问题里“能省就省”看似相反实则都是为了让未来更从容。这就是贪心算法的一体两面。8.3 一道变形题的面试官提问套路面试官如果把 605 作为暖场题通常会追问三种变体“如果花坛不是数组而是一个环形花坛怎么处理”“如果相邻的距离限制不是 1而是 2怎么修改代码” “如果我想返回具体的种花方案而不只是想得到一个布尔值怎么改”环形变体的思路也好办把环形花坛拆成“首尾相邻”的场景只需特判首尾两个位置即可本质还是同一个扫描框架。距离限制改成 2 的话检查左右两位都要空。返回具体方案更简单我们已经在原数组上种花了方案已经体现在数组里。如果你能把这些扩展问题的思路都提前想一遍面试时被问到任何变体你都不会慌乱。在我看来刷题的意义不是背下某一道题而是把一类算法的思维方式内化。9. 我的面经总结与最后建议这道 LeetCode 605 大家千万别小看它。我在模拟面试中见过不少候选人数组题目造个轮子玩得飞起结果在这道“简单题”上翻船原因都是边界处理不严谨。基础差不是问题问题是明明可以直接靠“能种就种”四字真言解决的问题非要去背官方公式最后把自己绕进去。我个人的建议是凡是遇到简单题先问自己能不能用一个生活中的常识来理解。如果能把题目翻译成“一排座位能不能再坐人”“一排停车位能不能再塞一辆车”你就不太可能被公式带偏。代码这种东西思路活了写出来是顺手的事。最后分享一个我自己的小习惯。我刷完每道题后会手动在纸上跑三个用例全 0 数组、全 1 数组、以及一个“只有开头有空位”的数组。这三个用例基本能覆盖你能遇到的所有边界陷阱。种花问题也不例外。你把这步养成的习惯带到后面每一题刷题效率不会差的。希望这篇嚼碎了讲的方法能帮你稳稳拿下这道题。下一道题咱们继续一起手撕。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。