Start learning free

LESSON 459 OF 536 · GREEDY

Gas Station in JavaScript: 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

    JAVASCRIPT · GREEDY

    Gas Station in JavaScript, explained

    Problem overview

    The method canCompleteCircuit(gas, cost) describes n gas stations on a circular route. Station i gives gas[i] units of gas, and driving from station i to the next one costs cost[i]. After station n - 1 the route continues at station 0. The tank has no size limit and starts empty at the station you choose. At each station you take all its gas, then drive on, and you can drive a stretch only if the tank holds at least its cost.

    The method returns the index of the station to start from so that you drive once around the circle and get back to it, 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 at station 3 the tank holds 4, then 8 at station 4, 7 at station 0, 6 at station 1 and 5 at station 2, and the last stretch costs exactly 5. In the second example, gas = [2, 3, 4] and cost = [3, 4, 3], the stations hold 9 units of gas but the trip costs 10, so the answer is -1.

    Approach: skip every start that fails

    The brute force tries every start and simulates the full circle, which is O(n²). The lesson does it in one pass in O(n) time and O(1) extra space, with two ideas.

    First, check that a start can exist at all. Add up gas[i] - cost[i] over all the stations. If the total is negative, the trip costs more than all the gas there is, and no start can work. Return -1 at once.

    Second, find the start by driving once from station 0. Keep a candidate start, first 0, and a tank that holds the gas since that candidate. At each station add gas[i] - cost[i]. If the tank drops below 0, the car could not have driven the stretch after station i from start. Then move the candidate to station i + 1 and empty the tank.

    Skipping all the stations from the old start to i is safe. Starting at 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 start.

    The last candidate works because the total isn't negative. Each stretch skipped before it loses gas overall, but the gas left in the tank at the end is at least what those stretches lose together. Inside a skipped stretch the tank only goes below 0 at its very end, so with that leftover gas the car gets through each stretch and back to the start.

    Here is the pass over the first example. Each line shows the station, gas[i] - cost[i], the tank afterwards and the candidate:

    i 0  diff -2  tank 0  start 1  (reset)
    i 1  diff -2  tank 0  start 2  (reset)
    i 2  diff -2  tank 0  start 3  (reset)
    i 3  diff  3  tank 3  start 3
    i 4  diff  3  tank 6  start 3
    

    The candidate ends at 3, the answer. For the third example, gas = [5, 1, 2, 3, 4] and cost = [4, 4, 1, 5, 1], the pass moves the candidate to 2, then to 4:

    i 0  diff  1  tank 1  start 0
    i 1  diff -3  tank 0  start 2  (reset)
    i 2  diff  1  tank 1  start 2
    i 3  diff -2  tank 0  start 4  (reset)
    i 4  diff  3  tank 3  start 4
    

    Step by step

    1. Store n, the number of stations, as gas.length.
    2. Declare total with let and start it at 0. It adds up what each station gives minus what the stretch after it costs.
    3. Loop over the stations, with i from 0 while i < n.
    4. Add gas[i] - cost[i] to total with +=.
    5. Return -1 if total < 0, right after the loop. Otherwise a start exists, and the second pass finds it.
    6. Declare start with let and set it to 0. Station 0 is tried first, and the candidate moves only when it fails.
    7. Declare tank with let and set it to 0: the car starts empty.
    8. Loop over the stations again from 0 to n - 1. A single drive is enough.
    9. Take the gas and drive: add gas[i] - cost[i] to tank. Afterwards tank is the gas left on arriving at the next station.
    10. Test tank < 0. Exactly 0 is fine: the car arrives with an empty tank and can still take the next station's gas.
    11. Inside the if, move start to i + 1, the station after the stretch where the tank ran dry.
    12. Empty the tank by setting it to 0, because the trip begins again at the new start.
    13. Return start after the loop.

    The solution in JavaScript

    class Solution {
        canCompleteCircuit(gas, cost) {
            const n = gas.length;
            let total = 0;
            for (let i = 0; i < n; i++) {
                total += gas[i] - cost[i];
            }
            if (total < 0) {
                return -1;
            }
            let start = 0;
            let tank = 0;
            for (let i = 0; i < n; i++) {
                tank += gas[i] - cost[i];
                if (tank < 0) {
                    start = i + 1;
                    tank = 0;
                }
            }
            return start;
        }
    }
    

    The JavaScript details worth knowing:

    • const and let. n never changes, so it is const. total, start and tank change in the loops, so they use let.
    • The circle is never wrapped. There is no % n anywhere. The total check already guarantees that the stretches before the candidate are covered by the leftover gas, so one pass over the list is enough.
    • Two passes, still O(n). The method makes two separate loops over the stations, each with constant work per element. The time is about 2n steps, which is O(n).
    • Plain numbers. The values are at most 10,000 and there are at most 100,000 stations, so sums stay far below the point where JavaScript numbers stop being exact. The statement guarantees no overflow can matter.
    • Can start reach n? Only if the tank is negative after the last station, which would make total negative too. That case returned -1 earlier, so the method never returns an index past the end.

    Complexity

    Time: O(n). Two passes over the stations, each doing a constant amount of work per station.

    Space: O(1). Four numbers (n, total, start and tank) and the loop index, however many stations there are.

    Edge cases and common mistakes

    • One station. gas = [5], cost = [4] returns 0. gas = [4], cost = [5] returns -1. gas = [3], cost = [3] returns 0, because arriving with exactly zero left is allowed.
    • Total gas less than total cost. The first loop catches it. Without that check, the second loop would return a candidate that does not work.
    • Using tank <= 0. An empty tank is fine, so with <= the candidate would skip stations that work. For gas = [3] and cost = [3] the answer would move to 1, past the end of the list.
    • Forgetting to reset the tank. Carrying the negative value into the new candidate makes it look worse than it is.
    • Zero gas and zero cost. gas = [0, 0], cost = [0, 0] returns 0: nothing is ever spent, so the first station works. (The statement guarantees at most one working start, so inputs like this one are outside it, but the code is still safe on them.)

    Testing

    • gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2] gives 3.
    • gas = [2, 3, 4], cost = [3, 4, 3] gives -1.
    • gas = [5, 1, 2, 3, 4], cost = [4, 4, 1, 5, 1] gives 4.
    • gas = [5], cost = [4] gives 0.
    • gas = [4], cost = [5] gives -1.
    Keep going with JavaScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free