Problem overview
Stations sit on a circular route. Station i gives gas[i] units, and driving from station i to the next one costs cost[i]. 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. canCompleteCircuit(gas, cost) returns the index of the start from which you can go all the way around and come back, or -1 if there is none. At most one start works.
For 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 and take 5 (tank 8), drive to station 0 and take 1 (tank 7), and so on, arriving back at station 3 with exactly 5 for the last stretch. 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
The obvious plan tries every start and simulates the whole loop, which is O(n²) for up to 100,000 stations. A greedy idea does it in one pass, using two facts.
Fact 1: a total check. 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 works. If it is not negative, a start exists.
Fact 2: skipping failed starts. Drive once from station 0 to the last station, keeping the tank and a candidate start (first 0). When the tank drops below 0 on the stretch after station i, the car could not have driven it. Stations start to i cannot be the start either: from start the car reached each of them with 0 or more gas, and starting at one of them means arriving with an empty tank, which is never better, so it would run dry at the same place. So the next candidate is i + 1, with an empty tank.
Why is the last candidate right? From it, the car reaches the end of the list without running dry. Each stretch skipped before it loses gas overall, but the total is not negative, so the gas left at the end covers everything the skipped stretches lose together. Inside a skipped stretch the tank only went below 0 at its very end, so with that spare gas the car gets through each of them and back to the start.
Step by step
- Station count.
val n = gas.size. The route is a circle: after stationn - 1comes station 0. - The total. A
var totalstarts at 0, and a loop over0 until naddsgas[i] - cost[i]: what stationigives minus what the next stretch costs. - Reject impossible trips. If
total < 0, return -1 right away. - Candidate and tank.
var start = 0andvar tank = 0. Both change in the loop. - One drive. Loop over
0 until nand addgas[i] - cost[i]totank: after this line the tank holds the gas on arriving at the next station. - Dry tank. If
tank < 0, movestarttoi + 1and settankback to 0. Exactly 0 is fine: the car arrives empty and takes the next station's gas. - Return
start.
The solution in Kotlin
class Solution {
fun canCompleteCircuit(gas: IntArray, cost: IntArray): Int {
val n = gas.size
var total = 0
for (i in 0 until n) {
total += gas[i] - cost[i]
}
if (total < 0) {
return -1
}
var start = 0
var tank = 0
for (i in 0 until n) {
tank += gas[i] - cost[i]
if (tank < 0) {
start = i + 1
tank = 0
}
}
return start
}
}
Kotlin details worth knowing:
0 until nexcludesn. It is the right range for array indexes.0..nwould readgas[n]and throwArrayIndexOutOfBoundsException.varfor values that change.total,startandtankare reassigned in the loops.nnever changes, so it is aval.gas.size, notlength. Arrays in Kotlin havesize.- No overflow here. The largest possible sum is 100,000 × 10,000 = 1,000,000,000 in absolute value, which fits in an
Int(the limit is 2,147,483,647).
Tracing the examples
For the first example, each line shows gas[i] - cost[i], the tank after it (or dry), and the candidate afterwards:
i=0 gas-cost=-2 tank=dry start=1
i=1 gas-cost=-2 tank=dry start=2
i=2 gas-cost=-2 tank=dry start=3
i=3 gas-cost=3 tank=3 start=3
i=4 gas-cost=3 tank=6 start=3
The differences sum to 0, so a start exists, and the candidate that survives the pass is 3. For gas = [5, 1, 2, 3, 4] and cost = [4, 4, 1, 5, 1], the tank before any reset at each station is:
i=0 diff=1 tank=1 start=0
i=1 diff=-3 tank=-2 start=2
i=2 diff=1 tank=1 start=2
i=3 diff=-2 tank=-1 start=4
i=4 diff=3 tank=3 start=4
The tank goes negative at stations 1 and 3, which moves the candidate to 2 and then to 4. The function returns 4.
Complexity
Time: two passes over the stations, each with constant work per station: O(n).
Space: a few integers: O(1) extra.
Edge cases and common mistakes
- One station.
gas = [5],cost = [5]returns 0, since the trip costs exactly the gas. Withcost = [6]the total is negative and the answer is -1. - Exactly 0 is allowed. Test
tank < 0, nottank <= 0. Using<=would reject starts that arrive with an empty tank. - Skipping the total check. Without it, the loop returns some candidate even when no start works. For the second example the loop ends with
startat 2 instead of returning -1. - Moving to
iinstead ofi + 1. Stationiwas reached and its stretch failed, so it cannot be the start. - Forgetting to reset the tank. The new candidate begins with an empty tank, like the first one.
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 [5] gives 0
gas [5], cost [6] gives -1
gas [3, 1], cost [1, 3] gives 0
