Start learning free

LESSON 463 OF 540 · GREEDY

Gas Station in Scala: A Greedy O(n) Scan

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

    SCALA · GREEDY

    Gas Station in Scala, explained

    Problem overview

    There are n gas stations on a circular route. Station i has gas(i) units of gas, and driving from station i to the next one costs cost(i). After station n - 1 comes station 0 again. The tank is unlimited and starts empty. At each station you take all its gas, then drive on, and a stretch is possible only if the tank holds at least its cost.

    The method canCompleteCircuit returns the station to start from so that you drive once around the circle and get back, or -1 if no start works. At most one station works.

    For gas = [1, 2, 3, 4, 5] and cost = [3, 4, 5, 1, 2] the answer is 3. Starting there you take 4, and the tank goes 4, then 4 - 1 + 5 = 8, 8 - 2 + 1 = 7, 7 - 3 + 2 = 6, 6 - 4 + 3 = 5, and the last stretch costs exactly the 5 that is left. For gas = [2, 3, 4] and cost = [3, 4, 3] the answer is -1: there are 9 units of gas in all and the trip costs 10.

    Approach: skip every start that fails

    Trying every start and simulating the whole circle costs O(n^2). The statement asks for O(n) time and O(1) extra space, which a greedy scan gives. It rests on two observations.

    The total decides whether any start exists. Add up gas(i) - cost(i) over all stations. If the sum is negative, the trip costs more than all the gas there is, and no start can work. If it is not negative, a start exists, and the problem is to find it.

    A failure rules out a whole stretch of starts. Drive once from station 0 to the last station, keeping a candidate start (first 0) and the tank. If the tank drops below 0 after the stretch that follows station i, the car could not have driven it. None of the stations from the candidate to i can be the start either. The car reached each of them with 0 or more gas, and starting at one of them means arriving there with an empty tank, which is never better, so the tank would still go negative at the same point. The next candidate is i + 1, with an empty tank.

    The last candidate is the answer. The stretches skipped before it lose gas overall, but the total is not negative, so what the car has left at the end of the list covers them. Inside a skipped stretch the tank only goes negative at its very end, so with that gas the car gets through each stretch and back to the start.

    The tank on the first example, with the candidate after each station:

    i=0 tank=0 start=1
    i=1 tank=0 start=2
    i=2 tank=0 start=3
    i=3 tank=3 start=3
    i=4 tank=6 start=3
    

    The tank is reset to 0 each time the candidate moves. From station 3 on, it never drops below 0, and the method returns 3.

    Step by step

    1. Store the number of stations, gas.length, in a val named n.
    2. Declare total with var, set to 0. It adds up the gas taken minus the gas spent over all stations. It is a var because the loop changes it.
    3. Loop over every station with i <- 0 until n.
    4. Add gas(i) - cost(i) to total with +=: what station i gives minus what the stretch after it costs.
    5. Return -1 early if total < 0. No start can pay for the whole trip. Otherwise one exists.
    6. Declare start as a var set to 0, the station being tried. Station 0 is tried first.
    7. Declare tank as a var set to 0, the gas in the tank when driving from start.
    8. Loop over the stations again with i <- 0 until n. One drive from station 0 to the last one is enough.
    9. Take the gas and drive the stretch: add gas(i) - cost(i) to tank. Afterwards, tank is the gas left on arriving at the next station.
    10. Check for a dry tank with tank < 0. Exactly 0 is fine: the car arrives empty and can still take the next station's gas.
    11. Move start to i + 1, the station after the stretch where the tank ran dry.
    12. Empty the tank by setting it to 0, since the trip now begins again at the new start.
    13. End with start as the last expression.

    The solution in Scala

    class Solution {
        def canCompleteCircuit(gas: Array[Int], cost: Array[Int]): Int = {
            val n = gas.length
            var total = 0
            for (i <- 0 until n) {
                total += gas(i) - cost(i)
            }
            if (total < 0) {
                return -1
            }
            var start = 0
            var tank = 0
            for (i <- 0 until n) {
                tank += gas(i) - cost(i)
                if (tank < 0) {
                    start = i + 1
                    tank = 0
                }
            }
            start
        }
    }
    

    The Scala details worth a closer look:

    • val versus var. n never changes, so it is a val. total, start and tank change inside loops, so they are vars. Scala rejects an assignment to a val.
    • until excludes its end. 0 until n gives the indices 0 to n - 1, which are exactly the valid indices of the arrays. 0 to n would run one index past the end.
    • Early return. The return -1 sits inside an if, outside any closure, so it leaves the method directly. Everywhere else the method relies on the last expression, start, being the result.
    • start stays a valid index. When total is not negative, the final candidate is always less than n. If the tank were negative at the very last station, the sum of everything would be negative too, and the method would already have returned -1.
    • Int is enough. It is 32 bits and wraps on overflow, but with up to 100000 stations and values up to 10000, each sum stays within 10^9, well inside its range.

    Complexity

    Time: O(n). The method makes two passes over the stations, each doing constant work per station.

    Space: O(1). It keeps four integers and builds no extra array.

    Edge cases and common mistakes

    • One station. gas = [5] and cost = [4] give 0: the station pays for its own stretch. gas = [4] and cost = [5] give -1.
    • A dry tank at exactly 0. The test is tank < 0, not tank <= 0. In gas = [3, 1, 1] and cost = [1, 2, 2] the tank is exactly 0 after the last stretch, which is a success. With <= the start would move to station 3, which doesn't exist.
    • Forgetting the total check. Without it, the second loop returns some candidate even when the trip is impossible. For the second example it would return 2, which fails.
    • Not resetting the tank. When the candidate moves, the new trip starts empty. A leftover negative tank would make every later check fail.
    • Moving the start to i instead of i + 1. Station i is the one whose stretch could not be driven, so it is part of the failure.
    • The first station works. In gas = [3, 1, 1] and cost = [1, 2, 2] the tank after each stretch is 2, 1 and 0, so the start never moves and the method returns 0.

    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.
    • gas = [3, 1, 1], cost = [1, 2, 2] gives 0.
    Keep going with Scala500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free