Taro Logo

Minimum Number of Refueling Stops

Hard
Google logo
Google
2 views
Topics:
Greedy AlgorithmsDynamic Programming

A car travels from a starting position to a destination which is target miles east of the starting position.

There are gas stations along the way. The gas stations are represented as an array stations where stations[i] = [position<sub>i</sub>, fuel<sub>i</sub>] indicates that the i<sup>th</sup> gas station is position<sub>i</sub> miles east of the starting position and has fuel<sub>i</sub> liters of gas.

The car starts with an infinite tank of gas, which initially has startFuel liters of fuel in it. It uses one liter of gas per one mile that it drives. When the car reaches a gas station, it may stop and refuel, transferring all the gas from the station into the car.

Return the minimum number of refueling stops the car must make in order to reach its destination. If it cannot reach the destination, return -1.

Note that if the car reaches a gas station with 0 fuel left, the car can still refuel there. If the car reaches the destination with 0 fuel left, it is still considered to have arrived.

Example 1:

target = 1, startFuel = 1, stations = []

Output: 0 (We can reach the target without refueling.)

Example 2:

target = 100, startFuel = 1, stations = [[10,100]]

Output: -1 (We can not reach the target or even the first gas station).

Example 3:

target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]

Output: 2

Explanation: We start with 10 liters of fuel. We drive to position 10, expending 10 liters of fuel. We refuel from 0 liters to 60 liters of gas. Then, we drive from position 10 to position 60 (expending 50 liters of fuel), and refuel from 10 liters to 50 liters of gas. We then drive to and reach the target. We made 2 refueling stops along the way, so we return 2.

Solution


Naive Solution: Brute Force (Recursion with Backtracking)

One straightforward approach is to use recursion to explore all possible combinations of refueling stops. At each gas station, we have a choice: either stop and refuel or skip it and continue. We recursively explore both options and keep track of the minimum number of stops needed to reach the target.

Algorithm

  1. Define a recursive function solve(current_fuel, current_position, index, stops):
    • Base Case 1: If current_position + current_fuel >= target, we have reached the target. Return stops.
    • Base Case 2: If index >= len(stations), we have exhausted all gas stations. Return infinity (float('inf')) because we cannot reach the target.
    • Recursive Step: Explore two options:
      • Option 1: Don't stop at the current station.
        • Calculate the distance to the next station or the target.
        • If current_fuel < distance, return infinity.
        • Recursively call solve(current_fuel - distance, stations[index][0], index + 1, stops).
      • Option 2: Stop at the current station.
        • Calculate the distance to the current station.
        • If current_fuel < distance, return infinity.
        • Recursively call solve(stations[index][1] + current_fuel - distance, stations[index][0], index + 1, stops + 1).
    • Return the minimum of the two options.
  2. Call the solve function with initial fuel, starting position 0, index 0, and 0 stops.
  3. If the result is infinity, return -1. Otherwise, return the result.

Code (Python)

```python def min_refuel_stops_recursive(target, startFuel, stations): def solve(current_fuel, current_position, index, stops): if current_position + current_fuel >= target: return stops if index >= len(stations): return float('inf')

    # Option 1: Don't stop
    distance_to_next = stations[index][0] - current_position
    if current_fuel < distance_to_next:
        option1 = float('inf')
    else:
        option1 = solve(current_fuel - distance_to_next, stations[index][0], index + 1, stops)

    # Option 2: Stop
    if current_fuel < distance_to_next:
        option2 = float('inf')
    else:
        option2 = solve(stations[index][1] + current_fuel - distance_to_next, stations[index][0], index + 1, stops + 1)

    return min(option1, option2)

result = solve(startFuel, 0, 0, 0)
return result if result != float('inf') else -1

Example Usage:

target = 100 startFuel = 10 stations = [[10, 60], [20, 30], [30, 30], [60, 40]] print(min_refuel_stops_recursive(target, startFuel, stations)) # Output: 2


### Time Complexity

O(2<sup>N</sup>), where N is the number of gas stations. This is because, at each station, we explore two possibilities (stop or don't stop), leading to an exponential number of paths.

### Space Complexity

O(N), due to the depth of the recursion stack.

## Optimal Solution: Dynamic Programming or Greedy Approach

A more efficient approach is to use dynamic programming or a greedy algorithm using a max-heap.  The greedy approach is generally preferred due to its efficiency.

### Greedy Algorithm with Max-Heap

The idea is to maintain a max-heap of fuel amounts from the stations we have passed. If we run out of fuel before reaching the next station or the target, we refuel from the station with the most fuel (if available) in the heap.

### Algorithm

1.  Initialize a max-heap `heap` (e.g., using Python's `heapq` with negative values to simulate a max-heap).
2.  Initialize `current_fuel` with `startFuel` and `stops = 0`.
3.  Iterate through the stations:
    *   Calculate the distance to the next station.
    *   While `current_fuel < distance`:
        *   If the heap is empty, it means we cannot reach the next station. Return -1.
        *   Refuel by adding the largest fuel amount from the heap to `current_fuel`.
        *   Increment `stops`.
    *   Subtract the distance from `current_fuel`.
    *   Add the fuel from the current station to the heap.
4.  After iterating through all stations, repeat the refueling process (as in step 3) until we can reach the target.
5.  Return the `stops`.

### Code (Python)

```python
import heapq

def min_refuel_stops_greedy(target, startFuel, stations):
    heap = []  # Max-heap to store fuel amounts
    current_fuel = startFuel
    stops = 0
    stations.append((target, 0)) # Append target as the last station
    last_position = 0

    for position, fuel in stations:
        distance = position - last_position

        while current_fuel < distance:
            if not heap:
                return -1
            current_fuel += -heapq.heappop(heap)
            stops += 1

        current_fuel -= distance
        heapq.heappush(heap, -fuel)
        last_position = position

    return stops


# Example Usage:
target = 100
startFuel = 10
stations = [[10, 60], [20, 30], [30, 30], [60, 40]]
print(min_refuel_stops_greedy(target, startFuel, stations))  # Output: 2

Time Complexity

O(N log N), where N is the number of gas stations. This is because we iterate through each station once, and for each station, we may perform heap operations (push and pop), which take O(log N) time.

Space Complexity

O(N), as the max-heap can contain at most all the fuel amounts from the gas stations.

Edge Cases

  • Cannot reach the first station: If the startFuel is not enough to reach the first gas station, the algorithm should return -1.
  • Target unreachable: If, even after using all the fuel in the heap, the target is still unreachable, return -1.
  • No stations: If there are no stations, check if startFuel is enough to reach the target directly. If so, return 0; otherwise, return -1.
  • Zero fuel at a station: If the car reaches a station with 0 fuel, it can still refuel there.
  • Zero fuel at the target: If the car reaches the target with 0 fuel left, it is considered to have arrived.