605. 种花问题
问题描述
假设有一个很长的花坛,一部分地块种植了花,另一部分没有。花不能种植在相邻的地块上,否则它们会争夺水源,两者都会死亡。
给你一个整数数组 flowerbed
表示花坛,由若干 0
和 1
组成,其中 0
表示没种植花,1
表示种植了花。另有一个数 n
,能否在不打破种植规则的情况下种入 n
朵花?
返回 能否 在不违反规则的情况下种入 n
朵花。
示例:
输入: flowerbed = [1,0,0,0,1], n = 1
输出: true
解释: 可以在中间的0处种花,得到[1,0,1,0,1]
算法思路
核心思想:贪心算法
局部最优
:尽可能早地种花(从左到右扫描)全局最优
:最大化可种植数量- 判断条件:只要最大可种植数量 ≥ n,就返回 true
关键:
- 只有当
flowerbed[i] == 0
且其左右邻居都为 0 时,才能在位置i
种花 - 边界情况:首尾位置只有一侧有邻居
优化:
- 一次遍历:从左到右扫描,遇到可种位置立即种花
- 原地修改:种花后将
flowerbed[i]
改为 1,影响后续判断
代码实现
class Solution {
/**
* 判断是否能在花坛中种入 n 朵花而不违反相邻规则
*
* @param flowerbed 花坛数组,0表示空地,1表示已种花
* @param n 需要种的花的数量
* @return 是否能成功种入n朵花
*/
public boolean canPlaceFlowers(int[] flowerbed, int n) {
// 特殊情况:不需要种花
if (n == 0) {
return true;
}
int count = 0; // 记录实际能种的花的数量
for (int i = 0; i < flowerbed.length; i++) {
// 当前位置为空地,才可能种花
if (flowerbed[i] == 0) {
// 检查左右邻居是否都为空
boolean leftEmpty = (i == 0) || (flowerbed[i - 1] == 0);
boolean rightEmpty = (i == flowerbed.length - 1) || (flowerbed[i + 1] == 0);
// 如果左右都为空,则可以种花
if (leftEmpty && rightEmpty) {
flowerbed[i] = 1; // 种花(原地修改)
count++; // 种花数量加1
// 优化:如果已达到目标,提前返回
if (count >= n) {
return true;
}
}
}
}
// 检查是否种够了n朵花
return count >= n;
}
}
算法分析
时间复杂度:O(m)
- 其中 m 是
flowerbed
数组的长度 - 最多遍历一次数组
- 提前终止优化在某些情况下能减少实际运行时间
空间复杂度:O(1)
- 只使用常数额外空间
- 原地修改输入数组(符合题目要求)
算法过程
flowerbed = [1,0,0,0,1], n = 1
i | flowerbed | 当前值 | leftEmpty | rightEmpty | 可种? | 操作 | count |
---|---|---|---|---|---|---|---|
0 | [1,0,0,0,1] | 1 | - | true | 否 | 跳过 | 0 |
1 | [1,0,0,0,1] | 0 | false (1) | true (0) | 否 | 跳过 | 0 |
2 | [1,0,0,0,1] | 0 | true (0) | true (0) | 是 | 种花→1 | 1 |
- | - | - | - | - | - | 提前返回 true | - |
结果:true
flowerbed = [1,0,0,0,1], n = 2
i | flowerbed | 当前值 | leftEmpty | rightEmpty | 可种? | 操作 | count |
---|---|---|---|---|---|---|---|
0 | [1,0,0,0,1] | 1 | - | true | 否 | 跳过 | 0 |
1 | [1,0,0,0,1] | 0 | false | true | 否 | 跳过 | 0 |
2 | [1,0,0,0,1] | 0 | true | true | 是 | 种花→1 | 1 |
3 | [1,0,1,0,1] | 0 | false (1) | false (1) | 否 | 跳过 | 1 |
4 | [1,0,1,0,1] | 1 | - | - | 否 | 跳过 | 1 |
结果:false(只能种1朵,需要2朵)
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:标准示例
int[] bed1 = {1,0,0,0,1};
System.out.println("Test 1: " + solution.canPlaceFlowers(bed1, 1)); // true
// 测试用例2:需要种2朵
int[] bed2 = {1,0,0,0,1};
System.out.println("Test 2: " + solution.canPlaceFlowers(bed2, 2)); // false
// 测试用例3:单地块
int[] bed3 = {0};
System.out.println("Test 3: " + solution.canPlaceFlowers(bed3, 1)); // true
// 测试用例4:全空地
int[] bed4 = {0,0,0,0,0};
System.out.println("Test 4: " + solution.canPlaceFlowers(bed4, 3)); // true
// 可种在0,2,4位置
// 测试用例5:全已种
int[] bed5 = {1,1,1,1};
System.out.println("Test 5: " + solution.canPlaceFlowers(bed5, 1)); // false
// 测试用例6:不需要种花
int[] bed6 = {1,0,1};
System.out.println("Test 6: " + solution.canPlaceFlowers(bed6, 0)); // true
// 测试用例7:边界情况
int[] bed7 = {0,0,1,0,0};
System.out.println("Test 7: " + solution.canPlaceFlowers(bed7, 1)); // true
// 可在位置0或4种花
// 测试用例8:交替模式
int[] bed8 = {0,1,0,1,0};
System.out.println("Test 8: " + solution.canPlaceFlowers(bed8, 1)); // false
// 没有连续两个0
// 测试用例9:长序列
int[] bed9 = {0,0,1,0,0,0,1,0,0};
System.out.println("Test 9: " + solution.canPlaceFlowers(bed9, 2)); // true
// 可在位置0,4,8种花,最多种3朵
}
关键点
-
边界处理:
- 首位置(i=0):没有左邻居,视为左空
- 尾位置(i=length-1):没有右邻居,视为右空
- 使用
(i == 0)
和(i == length-1)
判断
-
贪心正确性:
- 尽早种花不会影响后续最优解
- 如果跳过一个可种位置,后续可种位置不会增加
- 局部最优导致全局最优
-
原地修改:
- 种花后将
flowerbed[i] = 1
- 影响后续位置的判断(正确反映种植规则)
- 种花后将
-
提前终止:
- 一旦种够
n
朵花,立即返回true
- 避免不必要的遍历
- 一旦种够
常见问题
-
为什么不计算理论最大值再比较?
虽然可以数学计算最大可种数量,但贪心模拟更直观且代码简洁。两种方法时间复杂度相同。 -
能否不修改原数组?
可以,但需要额外空间记录已种花的位置。原地修改更节省空间。 -
如何处理极端情况?
- 空数组:
flowerbed.length == 0
,返回n == 0
- 大
n
值:提前终止优化能快速处理
- 空数组:
-
算法的鲁棒性:
代码已处理所有边界情况,包括:- 单元素数组
- 全0或全1数组
- n=0的特殊情况
-
是否有数学公式解?
可以,通过计算连续0的段长来计算最大可种数量,但实现更复杂:// 对每段连续k个0,可种花数量为 (k+1)/2 // 需要特殊处理首尾段
贪心模拟更直观易懂。