1. 问题重述与理解
- 种花问题是一个经典的数组操作题目,题目描述如下:
我们有一个很长的花坛,用数组flowerbed表示,其中:
- 0表示该位置没有种花
- 1表示该位置已经种了花
现在给定一个整数n,问能否在不违反"相邻地块不能同时种花"的规则下,在花坛中再种入n朵花。如果可以返回true,否则返回false。
1.1 题目约束条件
- 花坛长度限制:1 <= flowerbed.length <= 2 * 10^4
- 数组元素只能是0或1
- 初始花坛已经满足不相邻种花的条件
- 要种的n朵花必须满足0 <= n <= flowerbed.length
1.2 示例分析
示例1:
输入:flowerbed = [1,0,0,0,1], n = 1
输出:true
解释:可以在中间的0位置种一朵花,变成[1,0,1,0,1]
示例2:
输入:flowerbed = [1,0,0,0,1], n = 2
输出:false
解释:无法在不违反规则的情况下种两朵花
2. 新手友好解法详解
2.1 核心思路
我推荐的方法是"逐个位置判断法",这种方法的特点是:
- 直观易懂:完全按照人类的思考方式,从左到右检查每个位置
- 边界处理简单:通过在数组两端添加虚拟的0,简化边界条件判断
- 实时标记:一旦确定可以种花,立即标记该位置,避免重复计算
2.2 具体实现步骤
步骤1:处理边界条件
在原始数组的首尾各添加一个0,这样可以统一处理边界情况:
原始数组:[1,0,0,0,1]
处理后:[0,1,0,0,0,1,0]
步骤2:遍历数组
从第1个元素(原数组的第0个)到倒数第2个元素(原数组的最后一个)进行遍历
步骤3:判断当前位置能否种花
对于每个位置i,检查:
- 当前位置是否为0(flowerbed[i] == 0)
- 前一个位置是否为0(flowerbed[i-1] == 0)
- 后一个位置是否为0(flowerbed[i+1] == 0)
如果三个条件都满足,则可以种花:
- 将flowerbed[i]设为1
- 计数器count加1
步骤4:比较count与n
遍历结束后,如果count >= n,返回true,否则返回false
2.3 代码实现(Python)
python复制def canPlaceFlowers(flowerbed, n):
# 处理边界条件:在首尾各添加一个0
flowerbed = [0] + flowerbed + [0]
count = 0
for i in range(1, len(flowerbed)-1):
if flowerbed[i] == 0 and flowerbed[i-1] == 0 and flowerbed[i+1] == 0:
flowerbed[i] = 1
count += 1
return count >= n
3. 方法优势与复杂度分析
3.1 方法优势
- 直观易懂:完全按照"看到空地就检查能不能种"的直观思路实现
- 边界处理优雅:通过添加虚拟边界,避免了复杂的边界条件判断
- 一次遍历:只需要遍历数组一次,效率高
- 实时更新:种花后立即标记,防止重复计算
3.2 复杂度分析
- 时间复杂度:O(m),其中m是花坛长度。我们只需要遍历数组一次
- 空间复杂度:O(m),因为创建了一个新数组(可以优化为O(1))
3.3 与官方解法的对比
官方解法通常采用"分段统计"的方法,计算连续0的区间能种多少花。这种方法需要:
- 处理各种边界情况
- 记住分段统计的公式
- 对连续0的区间进行分类讨论
相比之下,我们的方法:
- 不需要记忆复杂公式
- 边界处理统一简单
- 代码实现更直观
4. 常见问题与优化建议
4.1 常见问题
-
为什么要在首尾添加0?
- 这样可以统一处理边界情况。例如原数组第一个位置是0时,不需要特殊判断它的左边
-
为什么遍历范围是1到len(flowerbed)-2?
- 因为我们添加了边界0,实际有效数据是从索引1到倒数第二个
-
这个方法会漏掉某些情况吗?
- 不会。这个方法会检查所有可能的位置,且一旦种花就会立即标记,不会漏掉任何可能
4.2 优化建议
- 空间优化:
可以不用创建新数组,直接在原数组上操作,通过条件判断处理边界:
python复制def canPlaceFlowers(flowerbed, n):
count = 0
length = len(flowerbed)
for i in range(length):
if flowerbed[i] == 0:
left = (i == 0) or (flowerbed[i-1] == 0)
right = (i == length-1) or (flowerbed[i+1] == 0)
if left and right:
flowerbed[i] = 1
count += 1
return count >= n
- 提前终止:
当count已经达到n时,可以提前终止循环:
python复制def canPlaceFlowers(flowerbed, n):
count = 0
length = len(flowerbed)
for i in range(length):
if flowerbed[i] == 0:
left = (i == 0) or (flowerbed[i-1] == 0)
right = (i == length-1) or (flowerbed[i+1] == 0)
if left and right:
flowerbed[i] = 1
count += 1
if count >= n: # 提前终止
return True
return count >= n
5. 实际面试中的技巧
-
先解释思路再写代码:
在面试中,不要直接开始写代码。先向面试官解释你的解题思路,特别是边界处理的考虑 -
讨论时间和空间复杂度:
写完代码后,主动分析算法的时间和空间复杂度 -
考虑优化方案:
即使给出了一个解法,也可以讨论可能的优化方向,如上面的空间优化和提前终止 -
测试用例设计:
准备几个典型的测试用例:- 全0的花坛
- 全1的花坛
- 边界种花的情况
- n=0的特殊情况
6. 同类问题拓展
掌握这个解法后,可以尝试解决类似的数组操作问题:
- 会议室问题:给定一组会议时间,问是否能全部安排
- 加油站问题:环形路线上加油站问题
- 跳跃游戏:数组中的每个元素代表可以跳跃的最大长度
这类问题的共同特点是:
- 都需要遍历数组
- 需要在遍历过程中做出局部最优决策
- 通常有贪心算法的解法
7. 个人心得
在实际编码和面试中,我有以下几点体会:
- 简单即美:有时候最直观的解法就是最好的解法,不要过度追求复杂的优化
- 边界测试:一定要测试各种边界情况,特别是数组的第一个和最后一个元素
- 代码可读性:清晰的代码比聪明的代码更重要,特别是面试场景下
- 逐步优化:先写出一个正确但可能不是最优的解法,然后再考虑优化
种花问题虽然简单,但它很好地展示了如何用简单的思路解决看似复杂的问题。通过这个例子,我们可以学到很多处理数组问题的通用技巧。