Gas Station (Circular Tour)
Find the unique start on a circular route from which a car can complete the loop, in one pass: whenever the running tank goes negative, restart from the next station.
Overview
There are n stations on a circle; station i provides gas[i] fuel and driving to station i+1 costs cost[i]. Starting with an empty tank, find a start index from which the car can complete the full circle, or return −1. A solution exists iff Σ gas ≥ Σ cost, and when it does, it is unique.
The greedy: track tank as the running sum of gas[i] − cost[i] from the current candidate start. If tank drops below zero at station i, no start in [start, i] can work, so set start = i + 1 and tank = 0. After one pass, start is the answer if total ≥ 0.
Intuition
A mental model before the formal terms.
Imagine driving and watching the fuel gauge. If you run dry between stations 3 and 4, starting anywhere between your original start and station 3 would only have been worse — you would have arrived at every one of those stations with less fuel than you did now (you got there with a non-negative tank plus whatever you started with). So the only hope is to start after the breakdown point.
Once the total gas covers the total cost, some prefix of the loop has the lowest running balance; starting right after that lowest point means every subsequent balance is measured from the bottom, so it never dips below zero.
How it works
- Initialize
total = 0,tank = 0,start = 0. - For each
i:diff = gas[i] − cost[i]; add to bothtotalandtank. - If
tank < 0: setstart = i + 1,tank = 0— every start in[start, i]fails. - After the loop, return
startiftotal ≥ 0, else−1.
Why it works
Greedy-choice property (skip lemma). Suppose starting at s the tank first goes negative when leaving station i. For any s < k ≤ i, the tank on arrival at k from s was ≥ 0, so the fuel available at k when starting from s is at least the fuel available when starting from k itself (which is 0). Hence starting at k reaches every station up to i with no more fuel than starting at s did, and also fails at i. All of s..i are eliminated at once — jumping to i + 1 skips nothing viable.
Sufficiency of `total ≥ 0`. Let P_j = Σ_{t<j} (gas[t] − cost[t]) be prefix balances and m the index of the minimum prefix. Starting at m, the balance after any number of steps is P_j − P_m ≥ 0 for j > m, and after wrapping, total + P_j − P_m ≥ 0 because total ≥ 0. So the circuit completes. The one-pass greedy ends exactly at that start, because it resets precisely when a new prefix minimum is reached.
Uniqueness: if total ≥ 0, the start found is the last reset; any earlier start was eliminated by the skip lemma and any later start is not reachable as a reset point.
Recognition
How to tell a problem wants this.
- Circular route, per-station gain and per-segment cost, "can you complete the circuit / from where".
- A running balance that must never go negative over a cyclic sequence — the problem is really "rotate the array so all prefix sums are non-negative".
- Constraints guaranteeing a unique answer are a tell that the greedy reset works.
Interactive visualization
Play, step, change the input. ← → and space work too.
No interactive visualization for this topic yet
Related visualizations are linked under Related.
Pseudocode
1total = tank = 0; start = 02for i in 0..n-1:3 diff = gas[i] - cost[i]4 total += diff; tank += diff5 if tank < 0: start = i + 1; tank = 06return start if total >= 0 else -1Implementations
1# Circular tour: gas[i] is the fuel at station i, cost[i] the fuel needed to2# reach station i+1. Find a start from which the tank never goes negative.3# One pass, no simulation of every candidate.4 5 61 · If total gas < total cost, no start can work — that check is separate7# from finding WHICH start works8def can_complete_circuit(gas: list[int], cost: list[int]) -> int:9 if not gas:10 return -1 # no stations, so no valid start index exists11 total = tank = 012 start = 013 14 for i, (g, c) in enumerate(zip(gas, cost)):15 gain = g - c16 total += gain17 tank += gain182 · A negative tank means no station in [start, i] can be the answer19 if tank < 0:20 start = i + 121 tank = 022 233 · Feasibility is decided by the total, not by the last surviving start24 return start if total >= 0 else -125 26 274 · Why skipping the whole prefix is safe: if the tank goes negative first28# at station i starting from s, then every station between s and i also fails29# before reaching i, because each of those starts has a smaller running sum.30def verify_circuit(gas: list[int], cost: list[int], start: int) -> bool:31 n = len(gas)32 if start < 0:33 return False34 tank = 035 for k in range(n):36 i = (start + k) % n37 tank += gas[i] - cost[i]38 if tank < 0:39 return False40 return True41 42 435 · The brute-force version, O(n^2), kept for comparison and testing44def can_complete_circuit_brute(gas: list[int], cost: list[int]) -> int:45 for s in range(len(gas)):46 if verify_circuit(gas, cost, s):47 return s48 return -1for i, (g, c) in enumerate(zip(gas, cost))walks index and both values together, which reads as the problem statement.total = tank = 0chains the initialisation of two independent accumulators.- The reset
start = i + 1; tank = 0discards the failed prefix;totalis deliberately not reset. return start if total >= 0 else -1puts the feasibility decision in one expression.- Python integers are unbounded, so neither accumulator can overflow regardless of route length.
ziptruncates to the shorter list silently;zip(gas, cost, strict=True)(3.10+) raises on a length mismatch, which is the safer spelling here.%returns a non-negative result for a positive modulus, so(start + k) % nis safe even for a negativestart— unlike C++ and JavaScript.- Arbitrary-precision integers remove the overflow concern entirely.
itertools.accumulate(g - c for g, c in zip(gas, cost))gives the prefix sums directly if a different formulation is wanted.
- Using plain
zipon mismatched lists and silently ignoring the tail. - Resetting
totalalong withtank, which breaks the feasibility test. - Returning
startwithout checkingtotal.
- Modulo sign matters for the wraparound: Python
%is always non-negative for a positive modulus, so(start + k) % nis safe even for a negative start; C++ and JS/TS would produce a negative index. - Overflow: Python is unbounded, C++ needs
long longaccumulators, and JS/TS are exact to 2^53. - Pairing two parallel arrays is unchecked in all four, but Python offers
zip(..., strict=True)to catch a length mismatch at runtime — the only built-in guard among them. - The
-1sentinel is conventional everywhere; only TypeScript could expressnumber | nullwithout breaking a widely known contract.
Complexity
Single pass; no second lap needed because the total check certifies the wrap-around.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Circular arrays where a running balance must stay non-negative and you need the starting rotation.
- Feasibility of a cyclic sequence of gains/costs with a single global resource.
- The tank has a capacity cap:
gas = [5, 0, 0],cost = [1, 1, 1], capacity 2. Total 5 ≥ 3 and the greedy returns 0, but with a 2-unit tank you run dry before station 2 — the surplus argument assumes fuel carries over unbounded. Simulate with the cap instead. - You need all valid starts or the start minimizing something else (e.g. minimum initial fuel to buy) — compute prefix sums and use the minimum-prefix argument directly.
- Non-circular version with variable refuel amounts and a limited number of stops (Minimum Refueling Stops) — that is a max-heap greedy or DP, not this reset trick.
Alternatives
Common mistakes
- Resetting
startbut forgetting to resettank(or vice versa). - Running two laps or an
O(n²)simulation from every start — the single pass with thetotalcheck is enough. - Returning
startwithout checkingtotal ≥ 0;startcan ben(out of range) when no solution exists. - Using
tank <= 0as the reset condition — a tank of exactly 0 on arrival is fine.
Interview patterns
- Gas Station (LeetCode 134) — be ready to prove the skip lemma.
- Minimum-prefix rotation: "rotate so all prefix sums are non-negative" (same idea via Prefix Sum).
- Contrast with Kadane's Algorithm: both are one-pass resets when the running value goes negative.
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Recognizing a sliding-window problemIntermediate
- Prefix sum or segment tree?Intermediate
- Minimum Size Subarray SumIntermediate
- Merge IntervalsIntermediate
- Subarray Sum Equals KIntermediate