Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1326. Minimum Number of Taps to Open to Water a Garden - Leetcode Solution

Problem Description

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.

  • Each tap can only be opened once.
  • You may choose any subset of taps to open.
  • There is always at most one valid solution.
  • Do not reuse taps or intervals.

Input: n (integer), ranges (list of integers of length n+1)
Output: Minimum number of taps to open, or -1 if not possible.

Thought Process

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.

Solution Approach

To solve the problem efficiently, we can use a greedy algorithm. Here’s how we break down the solution:

  1. Convert tap ranges to intervals: For each tap i, compute the interval [max(0, i - ranges[i]), min(n, i + ranges[i])] that it can water.
  2. Record the farthest reach from each starting point: For each interval, update an array maxReach where maxReach[start] is the farthest right we can reach from start using any tap.
  3. Greedy coverage: Starting from 0, always jump to the farthest reachable point within the current coverage. Each time you extend your coverage, count it as opening a tap.
  4. Early stopping: If at any point you can't move forward (i.e., the next position is not reachable), return -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 Walkthrough

Example: n = 5, ranges = [3,4,1,1,0,0]

  1. Calculate intervals:
    • Tap 0: [0,3]
    • Tap 1: [0,5]
    • Tap 2: [1,3]
    • Tap 3: [2,4]
    • Tap 4: [4,4]
    • Tap 5: [5,5]
  2. Build maxReach:
    • For start=0, maxReach[0]=max(3,5)=5 (from Tap 0 and Tap 1)
    • For start=1, maxReach[1]=3 (from Tap 2)
    • For start=2, maxReach[2]=4 (from Tap 3)
    • For start=4, maxReach[4]=4 (from Tap 4)
    • For start=5, maxReach[5]=5 (from Tap 5)
  3. Greedy coverage:
    • Start at 0, current_end=0, next_end=5 (from maxReach[0])
    • Open tap, move to current_end=5 (garden covered)
  4. Result: Only 1 tap (Tap 1) is needed to cover the whole garden.

Time and Space Complexity

  • Brute-force approach: Tries all subsets of taps, which is O(2n+1) time and not feasible for large n.
  • Optimized (greedy) approach:
    • Building the maxReach array: O(n)
    • Greedy scan through the garden: O(n)
    • Total time complexity: O(n)
    • Space complexity: O(n) for the maxReach array

The greedy algorithm is highly efficient and suitable for large inputs.

Summary

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.

Code Implementation

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;
};