134. Gas Station
Problem Description
You have n
gas stations arranged in a circle. Each station i
has gas[i]
amount of gas available. To travel from station i
to the next station (i+1)
, you need cost[i]
amount of gas.
You start with an empty gas tank at one of the stations. Your car has an unlimited capacity gas tank, meaning it can hold any amount of gas.
The goal is to determine if there's a starting station from which you can complete one full circle around all stations. If such a starting point exists, return its index. If it's impossible to complete the circuit, return -1
.
Key points:
- The route is circular: after station
n-1
, you return to station0
- You must visit all stations exactly once in clockwise order
- You start with 0 gas at your chosen starting station
- At each station, you first fill up with all available gas, then spend the required amount to reach the next station
- The problem guarantees that if a solution exists, it will be unique
For example, if gas = [1,2,3,4,5]
and cost = [3,4,5,1,2]
:
- Starting from station 3: gain 4 gas, spend 1 to reach station 4 โ tank has 3
- At station 4: gain 5 gas, spend 2 to reach station 0 โ tank has 6
- At station 0: gain 1 gas, spend 3 to reach station 1 โ tank has 4
- At station 1: gain 2 gas, spend 4 to reach station 2 โ tank has 2
- At station 2: gain 3 gas, spend 5 to reach station 3 โ tank has 0
- Successfully completed the circuit, so return 3
Intuition
The key insight is that if the total gas available is less than the total cost needed, it's impossible to complete the circuit regardless of where we start. So sum(gas) >= sum(cost)
is a necessary condition.
Now, instead of checking every possible starting point (which would be O(nยฒ)), we can use a clever two-pointer approach that starts from the end of the array and expands in both directions.
The algorithm maintains a window that represents our journey. We start with pointers i
and j
both at the last station (n-1
). The pointer j
moves forward (clockwise) to simulate traveling, while pointer i
can move backward (counter-clockwise) to include earlier stations as potential starting points.
Here's the brilliant part: when we accumulate gas and find our tank becomes negative (s < 0
), instead of starting over from a different position, we expand our window backward by moving i
to earlier stations. This is like saying "what if we had started earlier and already had some gas when we reached the problematic segment?"
Think of it this way: if we can't make it from station i
to station j
with the gas we have, we need to find an earlier starting point that gives us enough initial gas to overcome this deficit. By moving i
backward and adding gas[i] - cost[i]
to our tank, we're essentially prepending stations to our route.
The algorithm tracks:
s
: the running sum ofgas - cost
, representing our current tank balancecnt
: the number of stations we've considered (to ensure we check alln
stations exactly once)
If after considering all n
stations our tank balance s
is still negative, it means the total gas is less than the total cost, making the journey impossible. Otherwise, the position i
indicates where we should start to successfully complete the circuit.
This approach is efficient because each station is visited at most once, giving us O(n) time complexity, and we're guaranteed that if a solution exists, this method will find it.
Solution Approach
The implementation uses a two-pointer sliding window technique that starts from the end of the array and expands bidirectionally.
Initialization:
n = len(gas)
i = j = n - 1 # Both pointers start at the last station
cnt = s = 0 # cnt tracks stations visited, s tracks gas balance
Main Algorithm:
The outer while
loop continues until we've examined all n
stations (cnt < n
):
-
Forward Movement (j pointer):
s += gas[j] - cost[j] # Add net gas from current station j cnt += 1 # Increment station counter j = (j + 1) % n # Move j forward (circularly)
We first process station
j
by adding its net gas contribution (gas[j] - cost[j]
) to our running sums
. Then we advancej
to the next station using modulo arithmetic to handle the circular nature. -
Backward Expansion (i pointer):
while s < 0 and cnt < n: i -= 1 # Move starting point backward s += gas[i] - cost[i] # Add gas from new starting station cnt += 1 # Increment station counter
When the gas balance
s
becomes negative, we can't complete the journey starting from the currenti
. Instead of resetting, we expand our window backward by decrementingi
and including earlier stations. This effectively asks: "What if we had started from an earlier station?"
Example Walkthrough:
Consider gas = [2,3,4]
and cost = [3,4,3]
:
- Start:
i = j = 2
,s = 0
,cnt = 0
- Step 1: Process station 2:
s = 4-3 = 1
,cnt = 1
,j = 0
- Step 2: Process station 0:
s = 1+2-3 = 0
,cnt = 2
,j = 1
- Step 3: Process station 1:
s = 0+3-4 = -1
,cnt = 3
,j = 2
- Step 4: Since
s < 0
, movei
back:i = 1
,s = -1+3-4 = -2
,cnt = 4
Since we've examined more than n
stations but s
is still negative, return -1
.
Final Check:
return -1 if s < 0 else i
After processing all stations, if s < 0
, the total gas is insufficient for the total cost, so no solution exists. Otherwise, station i
is the valid starting point.
Time Complexity: O(n) - each station is visited at most once Space Complexity: O(1) - only using a few variables
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with gas = [1,2,3,4,5]
and cost = [3,4,5,1,2]
.
Initial Setup:
n = 5
i = j = 4
(both pointers start at the last station, index 4)s = 0
(gas balance)cnt = 0
(stations processed)
Step 1: Process station 4
- Add net gas:
s = 0 + (5 - 2) = 3
- Increment counter:
cnt = 1
- Move j forward:
j = (4 + 1) % 5 = 0
- Since
s >= 0
, continue forward
Step 2: Process station 0
- Add net gas:
s = 3 + (1 - 3) = 1
- Increment counter:
cnt = 2
- Move j forward:
j = (0 + 1) % 5 = 1
- Since
s >= 0
, continue forward
Step 3: Process station 1
- Add net gas:
s = 1 + (2 - 4) = -1
- Increment counter:
cnt = 3
- Move j forward:
j = (1 + 1) % 5 = 2
- Now
s < 0
, so we need to expand backward!
Step 4: Expand backward (inner while loop)
- Move starting point back:
i = 4 - 1 = 3
- Add gas from new starting station 3:
s = -1 + (4 - 1) = 2
- Increment counter:
cnt = 4
- Now
s >= 0
, exit inner loop
Step 5: Process station 2
- Add net gas:
s = 2 + (3 - 5) = 0
- Increment counter:
cnt = 5
- Move j forward:
j = (2 + 1) % 5 = 3
Final Result:
- We've processed all 5 stations (
cnt = 5
) - Final gas balance
s = 0 >= 0
- Return starting position
i = 3
Verification: Starting from station 3 works!
- Station 3: gain 4, spend 1 โ balance = 3
- Station 4: gain 5, spend 2 โ balance = 6
- Station 0: gain 1, spend 3 โ balance = 4
- Station 1: gain 2, spend 4 โ balance = 2
- Station 2: gain 3, spend 5 โ balance = 0 โ
The algorithm efficiently found that station 3 is the correct starting point by expanding the window backward when needed, avoiding the need to check every possible starting position separately.
Solution Implementation
1class Solution:
2 def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
3 """
4 Find the starting gas station index from which we can complete a circular tour.
5 Uses a two-pointer approach to efficiently find the valid starting point.
6
7 Args:
8 gas: List of gas available at each station
9 cost: List of gas cost to travel from station i to station i+1
10
11 Returns:
12 Starting station index if circuit is possible, -1 otherwise
13 """
14 n = len(gas)
15
16 # Initialize pointers: start_index and end_index both at the last station
17 start_index = n - 1
18 end_index = n - 1
19
20 # Track number of stations visited and current gas balance
21 stations_visited = 0
22 gas_balance = 0
23
24 # Continue until we've checked all stations
25 while stations_visited < n:
26 # Add gas and subtract cost at current end position
27 gas_balance += gas[end_index] - cost[end_index]
28 stations_visited += 1
29
30 # Move end pointer forward (circular)
31 end_index = (end_index + 1) % n
32
33 # If gas balance becomes negative, extend from the start
34 while gas_balance < 0 and stations_visited < n:
35 # Move start pointer backward
36 start_index -= 1
37
38 # Add gas and subtract cost at new start position
39 gas_balance += gas[start_index] - cost[start_index]
40 stations_visited += 1
41
42 # If final balance is negative, circuit is impossible
43 return -1 if gas_balance < 0 else start_index
44
1class Solution {
2 public int canCompleteCircuit(int[] gas, int[] cost) {
3 int n = gas.length;
4
5 // Start and end pointers for the circular route
6 int startIndex = n - 1;
7 int endIndex = n - 1;
8
9 // Track the number of stations processed and current gas balance
10 int stationsProcessed = 0;
11 int gasBalance = 0;
12
13 // Process all stations using a two-pointer approach
14 while (stationsProcessed < n) {
15 // Add gas and subtract cost at the current end position
16 gasBalance += gas[endIndex] - cost[endIndex];
17 stationsProcessed++;
18
19 // Move end pointer forward (circular)
20 endIndex = (endIndex + 1) % n;
21
22 // If gas balance becomes negative, try extending from the start
23 while (gasBalance < 0 && stationsProcessed < n) {
24 // Move start pointer backward and include that station
25 startIndex--;
26 gasBalance += gas[startIndex] - cost[startIndex];
27 stationsProcessed++;
28 }
29 }
30
31 // If final balance is negative, no solution exists
32 // Otherwise, return the starting index
33 return gasBalance < 0 ? -1 : startIndex;
34 }
35}
36
1class Solution {
2public:
3 int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
4 int n = gas.size();
5
6 // Use two pointers to find a valid starting position
7 // startIdx: potential starting station (moves backward)
8 // endIdx: current station being visited (moves forward)
9 int startIdx = n - 1;
10 int endIdx = n - 1;
11
12 // Track the number of stations visited and current gas balance
13 int stationsVisited = 0;
14 int gasBalance = 0;
15
16 // Try to visit all stations
17 while (stationsVisited < n) {
18 // Add gas and subtract cost at current station (endIdx)
19 gasBalance += gas[endIdx] - cost[endIdx];
20 stationsVisited++;
21
22 // Move to next station in circular manner
23 endIdx = (endIdx + 1) % n;
24
25 // If gas balance becomes negative, try extending the route backward
26 // by including previous stations as part of the journey
27 while (gasBalance < 0 && stationsVisited < n) {
28 // Move start position backward to include one more station
29 startIdx--;
30
31 // Add the net gas from this new starting station
32 gasBalance += gas[startIdx] - cost[startIdx];
33 stationsVisited++;
34 }
35 }
36
37 // If final gas balance is negative, no solution exists
38 // Otherwise, return the starting index
39 return gasBalance < 0 ? -1 : startIdx;
40 }
41};
42
1/**
2 * Finds the starting gas station index from which you can complete a circular tour.
3 * Uses a two-pointer approach to efficiently find the valid starting point.
4 *
5 * @param gas - Array where gas[i] represents the amount of gas at station i
6 * @param cost - Array where cost[i] represents the gas cost to travel from station i to station i+1
7 * @returns The starting gas station index if the tour is possible, otherwise -1
8 */
9function canCompleteCircuit(gas: number[], cost: number[]): number {
10 const stationCount: number = gas.length;
11
12 // Start from the last station
13 let startIndex: number = stationCount - 1;
14 let currentIndex: number = stationCount - 1;
15
16 // Track the current gas surplus/deficit
17 let gasBalance: number = 0;
18
19 // Count the number of stations processed
20 let stationsProcessed: number = 0;
21
22 // Process all stations
23 while (stationsProcessed < stationCount) {
24 // Add gas and subtract cost at current station
25 gasBalance += gas[currentIndex] - cost[currentIndex];
26 stationsProcessed++;
27
28 // Move to the next station in circular manner
29 currentIndex = (currentIndex + 1) % stationCount;
30
31 // If gas balance is negative, try starting from an earlier station
32 while (gasBalance < 0 && stationsProcessed < stationCount) {
33 startIndex--;
34 gasBalance += gas[startIndex] - cost[startIndex];
35 stationsProcessed++;
36 }
37 }
38
39 // If final balance is negative, no valid starting point exists
40 return gasBalance < 0 ? -1 : startIndex;
41}
42
Time and Space Complexity
Time Complexity: O(n)
The algorithm uses two pointers i
and j
to traverse the gas stations. The key insight is that the variable cnt
keeps track of the total number of stations processed. The outer while
loop continues as long as cnt < n
, and in each iteration (including both the outer loop and inner loop), cnt
is incremented by 1. Since cnt
starts at 0 and stops when it reaches n
, the total number of operations is exactly n
. Therefore, each station is visited exactly once, resulting in a linear time complexity of O(n)
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space regardless of the input size. The variables used are:
n
: stores the length of the input arrayi
,j
: two pointers for tracking positionscnt
: counter for the number of stations processeds
: accumulator for the gas balance
All these variables are simple integers that don't scale with the input size, giving us a constant space complexity of O(1)
.
Common Pitfalls
1. Incorrect Handling of Negative Index When Moving Backward
The most critical pitfall in this solution is that when we decrement the start_index
pointer to move backward, it can become negative. In Python, negative indices have special meaning (they index from the end of the array), but in this algorithm, we need the actual circular previous position.
Problem Code:
while gas_balance < 0 and stations_visited < n: start_index -= 1 # This becomes negative! gas_balance += gas[start_index] - cost[start_index] # Wrong index! stations_visited += 1
When start_index = 0
and we decrement it, start_index
becomes -1
. In Python, gas[-1]
accesses the last element, which seems correct for circular behavior. However, if we continue decrementing, start_index
becomes -2
, -3
, etc., which doesn't give us the correct circular traversal.
Solution:
while gas_balance < 0 and stations_visited < n: start_index = (start_index - 1 + n) % n # Proper circular decrement gas_balance += gas[start_index] - cost[start_index] stations_visited += 1
2. Not Validating That We've Actually Completed a Full Circuit
Another pitfall is assuming that if gas_balance >= 0
after visiting n
stations, we've found a valid starting point. However, we need to ensure we're actually checking a complete circuit from our starting point.
Enhanced Solution with Validation:
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
n = len(gas)
# Quick check: if total gas < total cost, impossible
if sum(gas) < sum(cost):
return -1
start_index = n - 1
end_index = n - 1
stations_visited = 0
gas_balance = 0
while stations_visited < n:
gas_balance += gas[end_index] - cost[end_index]
stations_visited += 1
end_index = (end_index + 1) % n
while gas_balance < 0 and stations_visited < n:
start_index = (start_index - 1 + n) % n # Fixed!
gas_balance += gas[start_index] - cost[start_index]
stations_visited += 1
return -1 if gas_balance < 0 else start_index
3. Confusion About What the Algorithm Actually Does
Developers might misunderstand that this algorithm is checking a continuous window of stations rather than simulating a circular journey. The algorithm cleverly finds a starting point by expanding a window backward when needed, effectively checking if there's a contiguous segment of stations whose cumulative gas surplus can cover the entire circuit.
Key Insight: The algorithm works because if the total gas โฅ total cost, there must exist a starting point. By expanding backward when we hit negative balance, we're finding the optimal starting position that maximizes our initial gas accumulation.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donโt Miss This!