You are given a one-dimensional garden of length n
(from position 0
to n
). There are n + 1
taps located at points 0
through n
. Each tap i
can water an interval from max(0, i - ranges[i])
to min(n, i + ranges[i])
, where ranges[i]
is the reach of tap i
.
Your goal is to open the minimum number of taps so that the entire garden (the interval [0, n]
) is watered. If it is impossible to water the whole garden, return -1
.
Input: n
(integer), ranges
(list of integers of length n+1
)
Output: Minimum number of taps to open, or -1
if not possible.
At first glance, it seems we could try every possible combination of taps to find the smallest subset that covers the entire garden. However, this brute-force approach quickly becomes infeasible as n
grows, because the number of possible subsets is exponential.
Instead, we can think of this as a classic "interval covering" problem: each tap provides an interval it can water, and we want to select as few intervals as possible to cover the entire garden from 0
to n
.
This is similar to the "Jump Game II" or "Minimum Number of Intervals to Cover a Range" problem, where a greedy approach is often optimal. The main idea is to always pick the tap (interval) that extends our reach as far as possible at each step.
To solve the problem efficiently, we can use a greedy algorithm. Here’s how we break down the solution:
i
, compute the interval [max(0, i - ranges[i]), min(n, i + ranges[i])]
that it can water.
maxReach
where maxReach[start]
is the farthest right we can reach from start
using any tap.
0
, always jump to the farthest reachable point within the current coverage. Each time you extend your coverage, count it as opening a tap.
-1
.
This approach ensures that at every step, we make the optimal choice for minimal tap usage, similar to the greedy solution for interval covering.
Example: n = 5
, ranges = [3,4,1,1,0,0]
maxReach
array: O(n)maxReach
arrayThe greedy algorithm is highly efficient and suitable for large inputs.
The problem of finding the minimum number of taps to water the garden is a classic interval covering problem. By converting tap ranges into intervals and using a greedy approach to always extend coverage as far as possible, we achieve an efficient O(n) solution. This method is both simple and powerful, leveraging key insights from interval algorithms to minimize tap usage.
class Solution:
def minTaps(self, n: int, ranges: List[int]) -> int:
maxReach = [0] * (n + 1)
for i in range(n + 1):
left = max(0, i - ranges[i])
right = min(n, i + ranges[i])
maxReach[left] = max(maxReach[left], right)
taps = 0
curr_end = 0
next_end = 0
i = 0
while curr_end < n:
while i <= curr_end:
next_end = max(next_end, maxReach[i])
i += 1
if next_end == curr_end:
return -1
taps += 1
curr_end = next_end
return taps
class Solution {
public:
int minTaps(int n, vector<int>& ranges) {
vector<int> maxReach(n + 1, 0);
for (int i = 0; i <= n; ++i) {
int left = max(0, i - ranges[i]);
int right = min(n, i + ranges[i]);
maxReach[left] = max(maxReach[left], right);
}
int taps = 0, curr_end = 0, next_end = 0, i = 0;
while (curr_end < n) {
while (i <= curr_end) {
next_end = max(next_end, maxReach[i]);
++i;
}
if (next_end == curr_end) return -1;
++taps;
curr_end = next_end;
}
return taps;
}
};
class Solution {
public int minTaps(int n, int[] ranges) {
int[] maxReach = new int[n + 1];
for (int i = 0; i <= n; ++i) {
int left = Math.max(0, i - ranges[i]);
int right = Math.min(n, i + ranges[i]);
maxReach[left] = Math.max(maxReach[left], right);
}
int taps = 0, curr_end = 0, next_end = 0, i = 0;
while (curr_end < n) {
while (i <= curr_end) {
next_end = Math.max(next_end, maxReach[i]);
++i;
}
if (next_end == curr_end) return -1;
++taps;
curr_end = next_end;
}
return taps;
}
}
var minTaps = function(n, ranges) {
let maxReach = new Array(n + 1).fill(0);
for (let i = 0; i <= n; ++i) {
let left = Math.max(0, i - ranges[i]);
let right = Math.min(n, i + ranges[i]);
maxReach[left] = Math.max(maxReach[left], right);
}
let taps = 0, curr_end = 0, next_end = 0, i = 0;
while (curr_end < n) {
while (i <= curr_end) {
next_end = Math.max(next_end, maxReach[i]);
++i;
}
if (next_end === curr_end) return -1;
++taps;
curr_end = next_end;
}
return taps;
};