1. 问题背景与核心挑战
这道LeetCode困难题1326描述了一个现实中的灌溉系统优化问题:花园长度为n米,从位置0延伸到位置n。在位置i处安装了一个能覆盖区间[i - ranges[i], i + ranges[i]]的水龙头。我们的任务是选择最少数量的水龙头,使得整个花园[0, n]都能被灌溉覆盖。
这个问题看似简单,实则暗藏多个难点:
- 水龙头的覆盖区间可能相互重叠
- 需要找到全局最优解而非局部最优
- 时间复杂度必须控制在合理范围内(O(n)或O(nlogn))
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题转化与算法选择
2.1 从实际问题到算法模型
这个问题可以转化为经典的"区间覆盖问题":给定一个目标区间和若干子区间,选择最少的子区间来完全覆盖目标区间。在本题中:
- 目标区间是[0, n]
- 每个水龙头对应一个子区间[i - ranges[i], i + ranges[i]]
2.2 贪心算法的适用性分析
贪心算法是解决此类问题的高效选择,原因在于:
- 问题具有最优子结构性质:全局最优解包含局部最优解
- 贪心选择性质:每一步的局部最优能导向全局最优
- 时间复杂度可以优化到O(n)
注意:虽然动态规划也能解决,但贪心算法在时间和空间复杂度上更优
3. 贪心算法实现详解
3.1 预处理阶段:构建有效区间
首先需要处理原始数据,构建可用的区间数组:
python复制n = len(ranges)
intervals = []
for i in range(n + 1):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
intervals.append((left, right))
这一步确保所有区间都在[0, n]范围内,避免无效计算。
3.2 贪心策略实现
核心贪心策略分为以下几个步骤:
- 排序区间:按左端点升序,右端点降序排列
python复制intervals.sort()
- 初始化变量:
python复制res = 0
current_end = 0
next_end = 0
i = 0
- 主循环逻辑:
python复制while current_end < n:
# 找到能覆盖current_end且右端点最大的区间
while i <= n and intervals[i][0] <= current_end:
next_end = max(next_end, intervals[i][1])
i += 1
if next_end == current_end: # 无法继续扩展
return -1
current_end = next_end
res += 1
3.3 边界条件处理
需要特别注意几种边界情况:
- 花园长度为0时直接返回0
- 第一个水龙头不能覆盖起点0时直接返回-1
- 中间出现无法连接的情况返回-1
4. 复杂度分析与优化
4.1 时间复杂度
- 排序阶段:O(nlogn)
- 贪心选择阶段:O(n)
- 总体:O(nlogn)
4.2 空间复杂度
只需要O(n)的额外空间存储区间数组。
4.3 进一步优化
可以省略显式排序步骤,直接预处理构建一个数组,记录每个位置能覆盖的最远右端点:
python复制max_right = [0] * (n + 1)
for i in range(n + 1):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
max_right[left] = max(max_right[left], right)
这样可以将时间复杂度优化到O(n)。
5. 完整代码实现
python复制def minTaps(n, ranges):
# 预处理构建max_right数组
max_right = [0] * (n + 1)
for i in range(n + 1):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
max_right[left] = max(max_right[left], right)
# 贪心算法实现
res = 0
current_end = 0
next_end = 0
for i in range(n + 1):
if i > next_end:
return -1
if i > current_end:
res += 1
current_end = next_end
next_end = max(next_end, max_right[i])
return res if current_end >= n else -1
6. 测试用例与验证
6.1 基础测试用例
python复制# 示例1
n = 5
ranges = [3,4,1,1,0,0]
print(minTaps(n, ranges)) # 输出:1
# 示例2
n = 3
ranges = [0,0,0,0]
print(minTaps(n, ranges)) # 输出:-1
6.2 边界测试用例
python复制# 花园长度为0
n = 0
ranges = [0]
print(minTaps(n, ranges)) # 输出:0
# 单个水龙头完全覆盖
n = 5
ranges = [5,0,0,0,0,0]
print(minTaps(n, ranges)) # 输出:1
6.3 性能测试用例
python复制# 大规模测试
n = 10000
ranges = [1] * (n + 1)
print(minTaps(n, ranges)) # 输出:5000
7. 常见错误与调试技巧
7.1 典型错误模式
- 忽略区间预处理:直接使用原始ranges数组,导致区间越界
- 贪心策略错误:没有正确维护current_end和next_end的关系
- 边界条件遗漏:忘记处理n=0或无法覆盖的情况
7.2 调试建议
- 打印中间变量:在关键步骤打印current_end和next_end的值
- 可视化区间:对于小规模输入,画出区间覆盖图辅助理解
- 逐步验证:先确保预处理结果正确,再验证贪心逻辑
8. 算法扩展与变种
8.1 类似问题
- 跳跃游戏II(LeetCode 45):最小跳跃次数到达终点
- 视频拼接(LeetCode 1024):最少片段拼接覆盖整个视频
- 合并区间(LeetCode 56):合并所有重叠区间
8.2 问题变种
- 加权区间覆盖:每个水龙头有开启成本,求最小总成本
- 概率覆盖:每个水龙头有失效概率,求全覆盖的最大概率
- 动态维护:水龙头状态会随时间变化,需要动态调整
9. 实际工程应用
这类算法在实际中有广泛应用场景:
- 无线基站部署:最少基站覆盖目标区域
- 监控摄像头布置:最少摄像头覆盖监控区域
- 资源调度优化:最少资源满足所有任务需求
在实现这类系统时,除了算法本身,还需要考虑:
- 实时性要求
- 动态调整能力
- 故障容错机制
10. 个人实践心得
在解决这类区间覆盖问题时,我总结了几个关键点:
- 预处理很重要:先把原始数据转化为标准区间形式,能简化后续处理
- 贪心选择要明确:清楚定义什么是"最优"选择标准(如最远覆盖)
- 边界条件先行:先处理各种边界情况,避免主逻辑被污染
- 可视化辅助:画图能帮助理解复杂的区间关系
在实际编码中,我发现维护两个变量current_end和next_end是最容易出错的点。我的经验是:
- current_end表示当前已覆盖的区域
- next_end表示下一步能覆盖的最远位置
- 只有当i超过current_end时才需要开启新水龙头
