Start learning free

LESSON 459 OF 536 · GREEDY

Gas Station in Python: O(n) Greedy, One Pass

Practice O(n) Read the full explanation

There are n gas stations on a circular route, numbered from 0 to n - 1. Station i has gas[i] units of gas, and driving from station i to the next station costs cost[i] units. After the last station, n - 1, the next one is station 0 again.

Your car's tank has no size limit, and it starts empty at one of the stations. At every station you take all of its gas, then drive on to the next station, going around the circle in increasing order. You can drive a stretch only if the tank holds at least its cost.

Return the index of the station to start from so that you drive all the way around the circle once and get back to it, or -1 if no start works. When a start works, it is the only one.

 

Example 1:

Input: gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2]
Output: 3
Explanation: Start at station 3 and take its 4 units: the tank has 4.
Drive to station 4 (cost 1) and take 5: 4 - 1 + 5 = 8.
Drive to station 0 (cost 2) and take 1: 8 - 2 + 1 = 7.
Drive to station 1 (cost 3) and take 2: 7 - 3 + 2 = 6.
Drive to station 2 (cost 4) and take 3: 6 - 4 + 3 = 5.
Drive back to station 3: it costs 5, and the tank has exactly 5.
From any other start, the tank runs dry on the way.

Example 2:

Input: gas = [2, 3, 4], cost = [3, 4, 3]
Output: -1
Explanation: The stations have 2 + 3 + 4 = 9 units of gas in all, but the trip around
costs 3 + 4 + 3 = 10, so no start can work.

Example 3:

Input: gas = [5, 1, 2, 3, 4], cost = [4, 4, 1, 5, 1]
Output: 4
Explanation: From station 0 you reach station 1 with 5 - 4 = 1, but 1 + 1 = 2 is less than
the 4 it costs to go on. Starting at station 1 instead is no better: you would be there
with an empty tank instead of 1. From station 2 you reach station 3 with 2 - 1 = 1, but
1 + 3 = 4 is less than 5, and station 3 can't go on alone either. From station 4 the tank
has 3 at station 0, 4 at station 1, 1 at station 2, 2 at station 3 and 0 back at station 4.

 

Constraints:

  • n == gas.length == cost.length
  • 1 <= n <= 105
  • 0 <= gas[i], cost[i] <= 104
  • At most one station works as the start.

Note:

Your algorithm should run in O(n) time and use O(1) extra space.

The tutorial is greedy. First it adds up gas[i] - cost[i] over all the stations: if the total is negative, the trip around costs more than all the gas there is, and no start can work. Otherwise it drives once from station 0 to the last station, keeping the gas in the tank and a candidate start, first 0. Whenever the tank drops below 0 on the stretch after station i, the car couldn't have driven it: the candidate moves to station i + 1, and the tank starts again from empty. At the end, the candidate is the answer.

Why skipping all the stations from start to i is safe: starting at start, the car reached each of them with 0 or more gas in the tank. Starting at one of them instead means being there with an empty tank, which is never better, so the tank would still drop below 0 after station i. None of them can be the start, and the next station to try is i + 1.

Why the last candidate works: from it, the car reaches the end of the list without running dry. Each stretch it skipped before it loses gas overall, but the total isn't negative, so the gas left in the tank at the end is at least what all of them lose together. Inside a skipped stretch the tank only goes below 0 at its very end, so with that gas the car gets through each of them, and back to the start.

STEP 1 OF 13
    Ctrl + Enter to check

    PYTHON · GREEDY

    Gas Station in Python, explained

    Problem overview

    There are n stations on a circular road. Station i gives gas[i] units of gas, and driving from station i to the next costs cost[i]. After station n - 1 comes station 0 again. The tank has no size limit and starts empty. At each station you take all its gas, then drive on, but only if the tank holds at least the cost of the stretch.

    canCompleteCircuit(gas, cost) returns the station to start from so that you complete one full circle, or -1 if no start works. At most one start works.

    In the first example, gas = [1, 2, 3, 4, 5] and cost = [3, 4, 5, 1, 2], the answer is 3. Starting there you take 4, drive to station 4 (cost 1) and take 5, which leaves 8, and the tank never drops below what the next stretch needs, ending with exactly 5 for the last stretch. In the second example, gas = [2, 3, 4] and cost = [3, 4, 3], there are 9 units of gas for a trip that costs 10, so the answer is -1.

    Approach: one pass with a moving start

    The brute force tries each station as the start and simulates the whole circle, which takes O(n²) and is too slow for n up to 100,000. The note asks for O(n) time and O(1) extra space, which calls for a greedy idea built on two observations.

    The total decides whether a start exists

    Define the gain at station i as gas[i] - cost[i]: the gas you take there minus the cost of the stretch after it. If all the gains add up to a negative number, the trip costs more than all the gas there is, and no start can work. If the total is not negative, a start exists, and the only job left is to find it.

    A failure rules out every station before it

    Drive from station 0, tracking the gas in the tank, and keep a candidate start, first 0. When the tank drops below 0 after the stretch that follows station i, the car could not have made it from start. Moving the candidate to i + 1 and emptying the tank restarts the trip there.

    Skipping all the stations from start to i is safe. From start, the car reached each of them with 0 or more gas. Starting at one of them means being there with an empty tank, which is never better, so the tank would still drop below 0 after station i. None of them can be the answer.

    The last candidate works because it reaches the end of the list without running dry, and the total isn't negative. Every stretch it skipped loses gas overall, but the gas the candidate has left at the end is at least what those stretches lose together. Inside a skipped stretch the tank only goes negative at its very end, so with that gas the car gets through each skipped stretch and back to the start.

    On the third example, gas = [5, 1, 2, 3, 4] and cost = [4, 4, 1, 5, 1], the gains are 1, -3, 1, -2, 3. Their total is 0, so a start exists. Walking through them:

    i=0  gain  1  tank  1  start 0
    i=1  gain -3  tank -2  -> start 2, tank 0
    i=2  gain  1  tank  1  start 2
    i=3  gain -2  tank -1  -> start 4, tank 0
    i=4  gain  3  tank  3  start 4
    

    The answer is 4.

    Step by step

    1. Store n = len(gas). The route is a circle, so after station n - 1 comes station 0.
    2. Start total at 0. It adds up the gas taken minus the gas spent over all the stations.
    3. Loop i over range(n). One pass over the stations.
    4. Add gas[i] - cost[i] to total. A station whose gas is less than its stretch costs makes a negative contribution, a richer one a positive contribution.
    5. If total < 0, return -1. All the gas can't pay for the trip, so no start works. Otherwise a start exists.
    6. Set start = 0. Station 0 is tried first, and the start moves only when it fails.
    7. Set tank = 0. The car begins with an empty tank.
    8. Loop i over range(n) again. A single drive from station 0 to the last station is enough.
    9. Add gas[i] - cost[i] to tank. Afterwards tank is the gas left on arriving at the next station.
    10. If tank < 0, the car couldn't drive this stretch from start. A tank of exactly 0 is fine: the car arrives empty and takes the next station's gas.
    11. Move start to i + 1. This is the key step. No station from the old start to i can be the start either.
    12. Reset tank to 0. The trip begins again at the new start with an empty tank.
    13. Return start. The total check showed a start exists, and none of the skipped stations can be it.

    The solution in Python

    class Solution:
        def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
            n = len(gas)
            total = 0
            for i in range(n):
                total += gas[i] - cost[i]
            if total < 0:
                return -1
            start = 0
            tank = 0
            for i in range(n):
                tank += gas[i] - cost[i]
                if tank < 0:
                    start = i + 1
                    tank = 0
            return start
    

    Python details to keep in mind:

    • Two separate loops. The first computes total and the second moves start. They use the same expression, gas[i] - cost[i], but they answer different questions, so they keep separate variables.
    • start can reach n inside the loop. If the tank goes negative at the last station, start becomes n. That can't happen when the code reaches the final return, because a non-negative total means the last candidate always makes it to the end of the list.
    • No wrap-around indexing. Despite the circular route, the code never uses % or a second lap. The total check replaces the second lap.
    • Plain integers. Python integers don't overflow, so sums of up to 100,000 values of up to 10,000 need no care.

    Complexity

    Two passes over the stations, each doing constant work per station, take O(n) time. The code keeps only n, total, start, tank and the loop variable, so the extra space is O(1).

    Edge cases and common mistakes

    • One station. canCompleteCircuit([5], [4]) is 0 and canCompleteCircuit([4], [5]) is -1.
    • Exactly enough gas. When the total is exactly 0, a start can still work, as in the first example. Test total < 0, not total <= 0. For the same reason, a tank of exactly 0 must not trigger a restart.
    • Everything balanced. With gas = [3, 3] and cost = [3, 3] every stretch is just affordable from the first station, and the answer is 0.
    • Forgetting to reset the tank. Moving start without setting tank to 0 carries debt from the old start into the new one.
    • Returning early. Moving start to i + 1 is a guess that only the rest of the pass can confirm. The loop must run to the end of the list.
    • Skipping the total check. Without it, the code would return a candidate even when no start works.

    Testing

    canCompleteCircuit([1, 2, 3, 4, 5], [3, 4, 5, 1, 2]) -> 3
    canCompleteCircuit([2, 3, 4], [3, 4, 3])             -> -1
    canCompleteCircuit([5, 1, 2, 3, 4], [4, 4, 1, 5, 1]) -> 4
    canCompleteCircuit([5], [4])                         -> 0
    canCompleteCircuit([4], [5])                         -> -1
    canCompleteCircuit([3, 3], [3, 3])                   -> 0
    
    Keep going with Python500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free