这道LeetCode困难题1326描述了一个现实中的灌溉系统优化问题:花园长度为n米,从位置0延伸到位置n。在x位置安装的水龙头可以覆盖区间[x - ranges[x], x + ranges[x]]。我们需要找到打开最少水龙头的组合,使得整个花园[0, n]被完全覆盖。
这个问题看似简单,实则暗藏多个算法难点。首先,水龙头的覆盖范围是动态变化的,每个位置x的覆盖半径ranges[x]各不相同。其次,水龙头之间可能存在重叠覆盖区域,如何避免重复计算是关键。最后,我们需要证明最少水龙头数量的正确性,这需要严谨的数学思维。
这类区间覆盖问题通常适合用贪心算法解决。贪心策略的核心思想是:在每一步选择中,选取能够覆盖当前未覆盖区域且能延伸到最远位置的区间。
具体到本题,我们需要:
虽然贪心算法更高效,但动态规划也是可行的解决方案。我们可以定义dp[i]表示覆盖[0,i]区间所需的最少水龙头数量。状态转移方程为:
dp[i] = min(dp[i], dp[left] + 1)
其中left是某个水龙头覆盖区间的左端点
不过这种方法时间复杂度为O(n^2),在n较大时效率不如贪心算法。
首先需要将水龙头的覆盖区间转换为标准形式:
python复制intervals = []
for i in range(len(ranges)):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
intervals.append((left, right))
然后按照左端点排序:
python复制intervals.sort()
python复制def minTaps(n, ranges):
intervals = []
for i in range(len(ranges)):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
intervals.append((left, right))
intervals.sort()
res = 0
curr_end = 0
next_end = 0
i = 0
while curr_end < n:
while i < len(intervals) and intervals[i][0] <= curr_end:
next_end = max(next_end, intervals[i][1])
i += 1
if next_end == curr_end:
return -1
curr_end = next_end
res += 1
return res
需要O(n)空间存储区间信息
可以省略显式的区间生成和排序步骤,直接在遍历时计算每个位置的覆盖范围:
python复制def minTaps(n, ranges):
max_reach = [0] * (n + 1)
for i in range(len(ranges)):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
max_reach[left] = max(max_reach[left], right)
res = 0
curr_end = 0
next_end = 0
for i in range(n + 1):
if i > next_end:
return -1
if i > curr_end:
res += 1
curr_end = next_end
next_end = max(next_end, max_reach[i])
return res
这种优化将时间复杂度降为O(n),空间复杂度仍为O(n)
边界条件处理不当:
贪心策略实现错误:
排序问题:
使用小规模测试用例验证边界条件:
python复制print(minTaps(3, [0,0,0,0])) # 应返回-1
print(minTaps(5, [3,4,1,1,0,0])) # 应返回1
打印中间变量检查算法状态:
python复制print(f"i={i}, curr_end={curr_end}, next_end={next_end}")
可视化覆盖范围:
绘制水龙头位置和覆盖区间,直观检查算法选择
这类区间覆盖问题在实际中有广泛应用:
理解这类问题的解法有助于设计更高效的资源分配方案。在解决实际问题时,还需要考虑更多约束条件,如:
在解决这道题时,我最初尝试了动态规划方法,但发现实现复杂且效率不高。转向贪心算法后,有几个关键点需要注意:
一个实用的调试技巧是:当算法出错时,先用手动计算小案例的预期结果,再与程序输出对比,这样可以快速定位问题所在。