-
Notifications
You must be signed in to change notification settings - Fork 0
155 — Gas Station
LeetCode 134 · Medium. There are n gas stations along a circular route, where the amount of gas at station i is gas[i]. You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station. You begin the journey with an empty tank at one of the gas stations. Given gas and cost, return the starting gas station's index if you can travel around the circuit once in the clockwise direction, otherwise return -1. If a solution exists, it is guaranteed to be unique.
Picture driving the loop and tracking your tank's running total as you go: at each station you gain gas[i] and immediately spend cost[i] reaching the next one. If starting from station 0 ever drives the tank negative at some station k, then station 0 can't be a valid start — but neither can any station between 0 and k. Why? Because every one of those stations was reached with a tank total that was 0 or higher at the start (it had to be, or the failure would've happened earlier), meaning starting from any of them only ever gives you a smaller or equal head start compared to carrying whatever surplus accumulated from station 0. So the moment the running tank goes negative, every station up to and including the failure point can be ruled out in one shot, and the next station becomes the new candidate start.
"Find the single starting point that lets you complete a circular route without running out" is the running-tank greedy tell: track a candidate start index and a running fuel total in one pass, and whenever the running total goes negative, discard every station tried so far at once (not just the current one) and restart the candidate from the very next station.
Try every possible starting station, simulating the entire circuit from each one to check if the tank ever goes negative.
func canCompleteCircuitBruteForce(_ gas: [Int], _ cost: [Int]) -> Int {
let n = gas.count
for start in 0..<n {
var tank = 0
var canComplete = true
for offset in 0..<n {
let i = (start + offset) % n
tank += gas[i] - cost[i]
if tank < 0 {
canComplete = false
break
}
}
if canComplete { return start }
}
return -1
}
// smoke test
print(canCompleteCircuitBruteForce([1, 2, 3, 4, 5], [3, 4, 5, 1, 2])) // 3
print(canCompleteCircuitBruteForce([2, 3, 4], [3, 4, 3])) // -1Big-O: O(n^2) time — every one of the n candidate starting stations simulates the full n-station circuit. O(1) extra space.
Make a single pass tracking the running tank total and the total fuel surplus/deficit across the whole circuit. Whenever the running tank goes negative, every station tried since the current candidate is disqualified at once, so the candidate resets to the very next station.
func canCompleteCircuit(_ gas: [Int], _ cost: [Int]) -> Int {
var totalTank = 0
var currentTank = 0
var start = 0
for i in 0..<gas.count {
let diff = gas[i] - cost[i]
totalTank += diff
currentTank += diff
if currentTank < 0 {
start = i + 1
currentTank = 0
}
}
return totalTank >= 0 ? start : -1
}
// smoke test — same cases as the brute force
print(canCompleteCircuit([1, 2, 3, 4, 5], [3, 4, 5, 1, 2])) // 3
print(canCompleteCircuit([2, 3, 4], [3, 4, 3])) // -1Big-O: O(n) time — a single left-to-right pass over both arrays. O(1) extra space.
The brute force re-simulates the entire circuit from scratch for every candidate start, even though most of that simulation work is identical to a run that already failed. The optimal version leans on the exchange argument sketched above: if the running tank first goes negative at station k while starting from candidate start, no station in [start, k] can possibly be a valid starting point either, because each of them was reached with a running total that was never worse than starting fresh from start — so a fresh start from any of them fails at least as early. That lets the scan discard a whole contiguous block of candidates in one step rather than testing them individually. Separately, totalTank >= 0 is exactly the condition for a solution to exist at all (if the circuit's total gas is less than its total cost, no starting point can possibly work), so checking it once at the end — rather than per-candidate — replaces n full simulations with one.
- Greedy — the running tank total and candidate start are state updated once per station and never revisited, backed by the exchange argument that ruling out a whole block of candidates at once is always safe.
-
Arrays and Strings — both versions scan
gasandcostas parallel backing arrays in a single left-to-right (or circular) pass.
Gas Station is one running tank total — the moment it goes negative, every station tried since the last candidate is ruled out at once, and the very next station becomes the new candidate.
- Why does the running tank going negative at station
krule out every station between the current candidate andk, not just the current candidate itself? - Why does checking
totalTank >= 0only once, at the very end, correctly decide whether a valid starting station exists at all? - Trace
canCompleteCircuit([1, 2, 3, 4, 5], [3, 4, 5, 1, 2])step by step. At which index doescurrentTankgo negative, and what doesstartbecome right after?