Hard
题目描述
在 x 轴上有一个一维花园。花园从点 0 开始,到点 n 结束(即花园的长度为 n)。
花园里有 n + 1 个水龙头,分别位于点 [0, 1, …, n]。
给定一个整数 n 和一个长度为 n + 1 的整数数组 ranges,其中 ranges[i](下标从 0 开始)表示如果打开第 i 个水龙头,它可以浇灌的区域为 [i - ranges[i], i + ranges[i]]。
返回能够浇灌整个花园的最少水龙头数目。如果花园无法被浇灌,返回 -1。
示例 1:
输入:n = 5, ranges = [3,4,1,1,0,0]
输出:1
解释:
点 0 处的水龙头可以浇灌区域 [-3,3]
点 1 处的水龙头可以浇灌区域 [-3,5]
点 2 处的水龙头可以浇灌区域 [1,3]
点 3 处的水龙头可以浇灌区域 [2,4]
点 4 处的水龙头可以浇灌区域 [4,4]
点 5 处的水龙头可以浇灌区域 [5,5]
只需要打开第二个水龙头就可以浇灌整个花园 [0,5]
示例 2:
输入:n = 3, ranges = [0,0,0,0]
输出:-1
解释:即使你开启所有的水龙头,你也无法浇灌整个花园。
提示:
- 1 <= n <= 10^4
- ranges.length == n + 1
- 0 <= ranges[i] <= 100
解题思路
这是一个经典的区间覆盖问题,我们需要用最少的区间覆盖整个范围 [0, n]。
解题思路:
区间转换:将每个水龙头的覆盖范围转换为区间 [left, right],其中第 i 个水龙头覆盖区间为 [max(0, i - ranges[i]), min(n, i + ranges[i])]
贪心策略:使用贪心算法选择最优的水龙头。对于每个需要覆盖的位置,我们选择能够覆盖该位置且右端点最远的水龙头。
算法流程:
- 从位置 0 开始,需要覆盖到位置 n
- 对于当前位置,找出所有能覆盖该位置的水龙头
- 选择其中右端点最远的那个
- 更新当前位置为选中水龙头的右端点
- 重复直到覆盖完整个花园
这种方法的核心思想是:在保证能够覆盖当前位置的前提下,选择覆盖范围最远的水龙头,这样可以最大化每一步的收益,从而用最少的水龙头覆盖整个花园。
代码实现
class Solution {
public:
int minTaps(int n, vector<int>& ranges) {
// 创建区间数组
vector<pair<int, int>> intervals;
for (int i = 0; i <= n; i++) {
int left = max(0, i - ranges[i]);
int right = min(n, i + ranges[i]);
if (left <= right) {
intervals.push_back({left, right});
}
}
// 按左端点排序
sort(intervals.begin(), intervals.end());
int taps = 0;
int currentPos = 0;
int i = 0;
while (currentPos < n) {
int maxReach = currentPos;
// 找到所有能覆盖当前位置的区间中右端点最远的
while (i < intervals.size() && intervals[i].first <= currentPos) {
maxReach = max(maxReach, intervals[i].second);
i++;
}
// 如果没有进展,说明无法覆盖
if (maxReach == currentPos) {
return -1;
}
taps++;
currentPos = maxReach;
}
return taps;
}
};
class Solution:
def minTaps(self, n: int, ranges: List[int]) -> int:
# 创建区间数组
intervals = []
for i in range(n + 1):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
if left <= right:
intervals.append([left, right])
# 按左端点排序
intervals.sort()
taps = 0
current_pos = 0
i = 0
while current_pos < n:
max_reach = current_pos
# 找到所有能覆盖当前位置的区间中右端点最远的
while i < len(intervals) and intervals[i][0] <= current_pos:
max_reach = max(max_reach, intervals[i][1])
i += 1
# 如果没有进展,说明无法覆盖
if max_reach == current_pos:
return -1
taps += 1
current_pos = max_reach
return taps
public class Solution {
public int MinTaps(int n, int[] ranges) {
// 创建区间数组
List<int[]> intervals = new List<int[]>();
for (int i = 0; i <= n; i++) {
int left = Math.Max(0, i - ranges[i]);
int right = Math.Min(n, i + ranges[i]);
if (left <= right) {
intervals.Add(new int[] {left, right});
}
}
// 按左端点排序
intervals.Sort((a, b) => a[0].CompareTo(b[0]));
int taps = 0;
int currentPos = 0;
int i = 0;
while (currentPos < n) {
int maxReach = currentPos;
// 找到所有能覆盖当前位置的区间中右端点最远的
while (i < intervals.Count && intervals[i][0] <= currentPos) {
maxReach = Math.Max(maxReach, intervals[i][1]);
i++;
}
// 如果没有进展,说明无法覆盖
if (maxReach == currentPos) {
return -1;
}
taps++;
currentPos = maxReach;
}
return taps;
}
}
var minTaps = function(n, ranges) {
const intervals = [];
for (let i = 0; i < ranges.length; i++) {
const left = Math.max(0, i - ranges[i]);
const right = Math.min(n, i + ranges[i]);
if (left <= right) {
intervals.push([left, right]);
}
}
intervals.sort((a, b) => a[0] - b[0]);
let taps = 0;
let currentEnd = 0;
let farthest = 0;
let i = 0;
while (currentEnd < n) {
while (i < intervals.length && intervals[i][0] <= currentEnd) {
farthest = Math.max(farthest, intervals[i][1]);
i++;
}
if (farthest <= currentEnd) {
return -1;
}
taps++;
currentEnd = farthest;
}
return taps;
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n) | 主要开销在排序区间数组 |
| 空间复杂度 | O(n) | 存储区间数组的空间 |