questions F-L
This commit is contained in:
178
backend/data/questions/gas-station.yaml
Normal file
178
backend/data/questions/gas-station.yaml
Normal file
@@ -0,0 +1,178 @@
|
||||
title: Gas Station
|
||||
slug: gas-station
|
||||
difficulty: medium
|
||||
leetcode_id: 134
|
||||
leetcode_url: https://leetcode.com/problems/gas-station/
|
||||
categories:
|
||||
- arrays
|
||||
patterns:
|
||||
- greedy
|
||||
|
||||
description: |
|
||||
There are `n` gas stations along a circular route, where the amount of gas at the i<sup>th</sup> station is `gas[i]`.
|
||||
|
||||
You have a car with an unlimited gas tank and it costs `cost[i]` of gas to travel from the i<sup>th</sup> station to its next (i + 1)<sup>th</sup> station. You begin the journey with an empty tank at one of the gas stations.
|
||||
|
||||
Given two integer arrays `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 there exists a solution, it is **guaranteed** to be **unique**.
|
||||
|
||||
constraints: |
|
||||
- `n == gas.length == cost.length`
|
||||
- `1 <= n <= 10^5`
|
||||
- `0 <= gas[i], cost[i] <= 10^4`
|
||||
- The input is generated such that the answer is unique
|
||||
|
||||
examples:
|
||||
- input: "gas = [1,2,3,4,5], cost = [3,4,5,1,2]"
|
||||
output: "3"
|
||||
explanation: "Start at station 3 (index 3) and fill up with 4 units of gas. Your tank = 0 + 4 = 4. Travel to station 4: tank = 4 - 1 + 5 = 8. Travel to station 0: tank = 8 - 2 + 1 = 7. Travel to station 1: tank = 7 - 3 + 2 = 6. Travel to station 2: tank = 6 - 4 + 3 = 5. Travel to station 3: cost is 5, gas is just enough. Return 3."
|
||||
- input: "gas = [2,3,4], cost = [3,4,3]"
|
||||
output: "-1"
|
||||
explanation: "Starting from any station, you cannot complete the circuit. For example, starting at station 2 with 4 gas, you can reach station 1 but cannot travel back to station 2 (requires 4 gas, you only have 3)."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Imagine you're planning a road trip around a circular route with gas stations. At each station, you can fill up some gas, but travelling to the next station costs some gas. The question is: **can you find a starting point where you never run out of fuel?**
|
||||
|
||||
The key insight comes from two observations:
|
||||
|
||||
**Observation 1: Total gas must be enough.** If the total gas available across all stations is less than the total cost to travel the entire circuit, it's impossible to complete the trip from *any* starting point. Conversely, if total gas >= total cost, a solution is **guaranteed** to exist.
|
||||
|
||||
**Observation 2: If you fail at station `j`, skip all previous candidates.** Suppose you start at station `i` and run out of gas at station `j`. You might think: "Maybe I should try starting at `i+1`?" But here's the crucial insight — if you couldn't reach `j` starting from `i` with a full journey's worth of gas from stations `i` to `j-1`, then starting from any station *between* `i` and `j` would give you even *less* gas (you'd miss the contributions from earlier stations). So **all stations from `i` to `j` are invalid starting points**.
|
||||
|
||||
This means when we fail, we can jump our candidate start directly to `j+1`, making this a linear-time algorithm.
|
||||
|
||||
approach: |
|
||||
We solve this using a **Single Pass Greedy Approach**:
|
||||
|
||||
**Step 1: Initialise tracking variables**
|
||||
|
||||
- `total_tank`: Tracks the cumulative surplus/deficit across all stations (used to check if a solution exists)
|
||||
- `current_tank`: Tracks the current fuel level from our candidate starting point
|
||||
- `start_station`: The index of our current candidate starting point, initialised to `0`
|
||||
|
||||
|
||||
|
||||
**Step 2: Iterate through each station**
|
||||
|
||||
- For each station `i`, calculate `gas[i] - cost[i]` (the net gain/loss at this station)
|
||||
- Add this value to both `total_tank` and `current_tank`
|
||||
- If `current_tank` becomes negative, it means we can't reach station `i+1` from our current `start_station`
|
||||
- When this happens, reset `start_station` to `i+1` and reset `current_tank` to `0`
|
||||
|
||||
|
||||
|
||||
**Step 3: Check feasibility and return**
|
||||
|
||||
- After the loop, if `total_tank >= 0`, a solution exists and `start_station` is our answer
|
||||
- If `total_tank < 0`, return `-1` (not enough total gas)
|
||||
|
||||
|
||||
|
||||
The greedy choice — skipping all stations between our failed start and the failure point — is valid because those intermediate stations would only give us less fuel to work with.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Trying Every Starting Point
|
||||
description: |
|
||||
A brute force approach would try starting at each station and simulate the entire trip:
|
||||
- For each starting station `i`, simulate travelling all `n` stations
|
||||
- Check if the tank ever goes negative
|
||||
|
||||
This results in **O(n^2) time complexity**. With `n` up to `10^5`, this means up to 10 billion operations — a guaranteed **Time Limit Exceeded (TLE)**.
|
||||
wrong_approach: "Nested loops simulating from each start"
|
||||
correct_approach: "Single pass with smart candidate elimination"
|
||||
|
||||
- title: Not Understanding Why We Can Skip Stations
|
||||
description: |
|
||||
When you fail at station `j` starting from station `i`, it might seem wasteful to skip directly to `j+1`. Why not try `i+1`?
|
||||
|
||||
The reason is mathematical: if you reached stations `i+1`, `i+2`, ..., `j-1` with non-negative fuel (otherwise you would have failed earlier), but still failed at `j`, then starting at any of those intermediate stations means you'd have *less* accumulated fuel when you reach `j`.
|
||||
|
||||
For example, if stations give net gains of `[+3, -1, -1, -2]` and you fail at index 3 starting from index 0, starting at index 1 means you miss the +3 from station 0, making failure even more certain.
|
||||
wrong_approach: "Increment start by 1 when failing"
|
||||
correct_approach: "Jump start to failure_point + 1"
|
||||
|
||||
- title: Forgetting to Check Total Feasibility
|
||||
description: |
|
||||
Just finding a valid `start_station` candidate isn't enough. You must verify that the **total gas >= total cost** for the entire circuit.
|
||||
|
||||
The `total_tank` variable serves this purpose. Even if we find a candidate, if `total_tank < 0` at the end, no solution exists.
|
||||
wrong_approach: "Only tracking current_tank"
|
||||
correct_approach: "Track both current_tank and total_tank"
|
||||
|
||||
key_takeaways:
|
||||
- "**Greedy elimination**: When a candidate fails, use problem structure to eliminate multiple candidates at once, not just one"
|
||||
- "**Global vs local tracking**: Use separate variables for local decisions (`current_tank`) and global feasibility (`total_tank`)"
|
||||
- "**Circular problems**: Often can be solved with a single linear pass by tracking cumulative state"
|
||||
- "**Proof intuition**: If total resources >= total cost, a valid starting point must exist — this is a key insight for many resource allocation problems"
|
||||
|
||||
time_complexity: "O(n). We traverse both arrays exactly once, performing constant-time operations at each station."
|
||||
space_complexity: "O(1). We only use three integer variables (`total_tank`, `current_tank`, `start_station`) regardless of input size."
|
||||
|
||||
solutions:
|
||||
- approach_name: Single Pass Greedy
|
||||
is_optimal: true
|
||||
code: |
|
||||
def can_complete_circuit(gas: list[int], cost: list[int]) -> int:
|
||||
# Track total surplus to check if solution exists
|
||||
total_tank = 0
|
||||
# Track current surplus from candidate start
|
||||
current_tank = 0
|
||||
# Our candidate starting station
|
||||
start_station = 0
|
||||
|
||||
for i in range(len(gas)):
|
||||
# Net gain/loss at this station
|
||||
net = gas[i] - cost[i]
|
||||
total_tank += net
|
||||
current_tank += net
|
||||
|
||||
# If we can't reach the next station from current start
|
||||
if current_tank < 0:
|
||||
# All stations from start to i are invalid
|
||||
# Try starting from the next station
|
||||
start_station = i + 1
|
||||
current_tank = 0
|
||||
|
||||
# If total gas >= total cost, solution exists at start_station
|
||||
# Otherwise, impossible to complete the circuit
|
||||
return start_station if total_tank >= 0 else -1
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — Single pass through both arrays.
|
||||
|
||||
**Space Complexity:** O(1) — Only three integer variables used.
|
||||
|
||||
The key insight is that if we fail to reach station `j` from station `i`, all stations between `i` and `j` are also invalid starting points. Combined with tracking total feasibility, this gives us an elegant linear solution.
|
||||
|
||||
- approach_name: Brute Force
|
||||
is_optimal: false
|
||||
code: |
|
||||
def can_complete_circuit(gas: list[int], cost: list[int]) -> int:
|
||||
n = len(gas)
|
||||
|
||||
# Try each station as a starting point
|
||||
for start in range(n):
|
||||
tank = 0
|
||||
can_complete = True
|
||||
|
||||
# Simulate travelling around the circuit
|
||||
for i in range(n):
|
||||
# Current station index (wrapping around)
|
||||
station = (start + i) % n
|
||||
# Fill up and travel to next station
|
||||
tank += gas[station] - cost[station]
|
||||
|
||||
# Ran out of gas before reaching next station
|
||||
if tank < 0:
|
||||
can_complete = False
|
||||
break
|
||||
|
||||
if can_complete:
|
||||
return start
|
||||
|
||||
return -1
|
||||
explanation: |
|
||||
**Time Complexity:** O(n^2) — For each of n starting points, we simulate travelling n stations.
|
||||
|
||||
**Space Complexity:** O(1) — Only tracking tank and loop variables.
|
||||
|
||||
This approach is correct but inefficient. It tries every possible starting station and simulates the full circuit. With n up to 10^5, this will cause TLE. Included to illustrate why the greedy optimisation is necessary.
|
||||
Reference in New Issue
Block a user