# 2025 年「資訊科技產業專案設計」課程作業 4
## 134. Gas Station
> [LeetCode 134. Gas Station](https://leetcode.com/problems/gas-station/description/)
### 原始題意
There are `n` gas stations along a circular route, where the amount of gas at the `i-th` 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-th` station to its next `(i + 1)` 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.
### 情境包裝
* **情境**:全球雲端服務的故障轉移路由測試 (Global Cloud Service Failover Routing Tests)。
* **細節**:
* 系統由 `n` 個邊緣區域 (Edge Regions) 組成一個邏輯環狀網路。
* 在區域 `i`,系統可以獲得 `g[i]` 個請求權杖 (Request Tokens)。
* 要將流量從區域 `i` 轉發到下一個區域 `(i+1) mod n`,系統必須消耗 `c[i]` 個請求權杖。
* **目標**:選擇一個起始區域進行演練,初始權杖為 0。若能完成一整圈回到起點且途中權杖餘額皆非負,回傳該起始索引;否則回傳 -1。
### 解法
#### Greedy (One Pass)
核心概念:如果總獲得權杖數小於總消耗,則無法繞一圈。若大於等於 0,則必然有解。利用貪婪演算法,當局部餘額小於 0 時,代表目前的起點無效,直接將起點重置為下一個位置。
```cpp
#include <vector>
using namespace std;
class Solution {
public:
int canCompleteCircuit(vector<int>& budget, vector<int>& cost) {
long long total = 0; // Global sum of diff
long long balance = 0; // Sum of diff from 'start' to current i
int start = 0;
for (int i = 0; i < (int)budget.size(); ++i) {
long long diff = (long long)budget[i] - cost[i];
total += diff;
balance += diff;
// Fail on segment start..i => skip all starts in [start..i]
// Because starting from any k in [start, i] would still result in negative balance at i
if (balance < 0) {
start = i + 1;
balance = 0;
}
}
return (total < 0) ? -1 : start;
}
};
```
### Follow up
#### 1. Return ALL valid starting region indices
如果題目移除了「唯一解」的保證,並要求回傳所有可能的起始點:
* **策略**:使用 **Prefix Sum + Monotonic Queue (Sliding Window Minimum)**。
* **實作思路**:
* 將陣列長度加倍 `(2n)` 以處理環狀結構。
* 計算差值 `diff[i] = g[i] - c[i]` 的前綴和 。
* 有效的起始點 必須滿足:對於所有 , 。
* 這等價於尋找區間內的最小值: 。
* 使用單調佇列 (Monotonic Deque) 維護大小為 的滑動視窗最小值,即可在 時間內找出所有符合條件的 。
```cpp
vector<int> getAllStartingStations(vector<int>& gas, vector<int>& cost) {
int n = gas.size();
vector<int> res;
// 1. Construct doubled difference array & Prefix Sum
// P[i] stores the cumulative sum of diff from 0 to i-1
vector<long long> P(2 * n + 1, 0);
for (int i = 0; i < 2 * n; ++i) {
P[i + 1] = P[i] + (long long)(gas[i % n] - cost[i % n]);
}
// 2. Sliding Window Minimum using Monotonic Queue (Deque)
// We need to check if min(P[s+1]...P[s+n]) >= P[s]
deque<int> dq; // Stores indices of P, keeping P[dq] increasing
// Iterate through the doubled array (representing the window end)
for (int i = 1; i < 2 * n; ++i) {
// Maintain monotonic property: remove elements larger than current P[i]
while (!dq.empty() && P[dq.back()] >= P[i]) {
dq.pop_back();
}
dq.push_back(i);
// Calculate corresponding start index 's'
// If current window end is 'i', and window size is 'n', then start is 'i - n'
if (i >= n) {
int s = i - n;
// Remove indices that are out of the current window (s, s+n]
// Valid range for min search is [s+1, s+n]
if (dq.front() <= s) {
dq.pop_front();
}
// Check condition: min(window) >= P[s]
if (s < n && P[dq.front()] >= P[s]) {
res.push_back(s);
}
}
}
return res;
}
```
### 模擬面試問答
* **🟠Interviewer—題目敘述**
* Hi, welcome to the interview.
* You’re operating a global cloud service with `n` edge regions connected in a logical ring for failover routing tests.
* At region `i`, your system can “gain” `g[i]` request tokens. To forward traffic from region `i` to the next region `(i+1) mod n`, the system must “spend” `c[i]` request tokens.
* You may choose ANY region as the starting point for a failover drill. The drill starts with 0 request tokens.
* Return the starting region index that allows the system to complete a full loop and return to the start. If it is impossible, return -1.
* Assume that if a solution exists, it is unique. Any questions?
* **👩💻Interviewee—確認限制 (Clarification)**
* Thanks. A few quick clarifications:
* Are `g[i]` and `c[i]` non-negative integers?
* What’s the expected constraint on `n`? For example, up to $10^5$?
* The routing path is fixed (`i` -> `(i+1) mod n`), no skipping regions, correct?
* **🟠Interviewer:**
* Correct on all. `n` can be up to $10^5$, and also we want an $O(N)$ solution.
* **👩💻Interviewee—舉例 (Sanity Check)**
* Let me use a classic example to trace the process.
* Suppose:
* `g = [1, 2, 3, 4, 5]` (tokens gained)
* `c = [3, 4, 5, 1, 2]` (tokens spent)
* First, I calculate the net difference `diff[i] = g[i] - c[i]`:
* `diff = [-2, -2, -2, +3, +3]`
* If I start at **index 0**:
* Current balance becomes `-2` immediately. **Fail.**
* If I start at **index 3** (where `diff` is positive):
* Step 1 (Region 3): Balance = `+3`. (OK)
* Step 2 (Region 4): Balance = `3 + 3 = 6`. (OK)
* Step 3 (Region 0): Balance = `6 + (-2) = 4`. (OK)
* Step 4 (Region 1): Balance = `4 + (-2) = 2`. (OK)
* Step 5 (Region 2): Balance = `2 + (-2) = 0`. (OK)
* Since the balance never drops below zero and we return to the start, **index 3** should be the valid starting point. Is that consistent?
* **🟠Interviewer:**
* Yes. And any edge cases?
* **👩💻Interviewee—邊界測試**
* All diff = 0: `total=0` and balance never drops below 0 => start remains 0, which is valid.
* Large values: Need to use `long long` for cumulative totals to avoid overflow.
* **🟠Interviewer:**
* What approach would you take?
* **👩💻Interviewee—解題思路**
* Brute force would try each starting region and simulate a full failover loop, which is $O(N^2)$ and too slow for $10^5$.
* I’ll use a **greedy one-pass approach** based on net token delta at each region: `diff[i] = g[i] - c[i]`.
* Two key observations:
1. If `total = sum(diff) < 0`, then total request tokens gained is less than required. No solution exists => return -1.
2. If `total >= 0`, then at least one solution exists.
* **One-pass algorithm**:
* Maintain `start` (candidate index) and `balance` (running sum from start).
* Scan `i` from `0` to `n-1`:
* `balance += diff[i]`
* If `balance < 0`: This means starting from `start`, we run out of tokens at `i`. Crucially, **NONE** of the regions in `[start..i]` can be a valid starting region.
* So we set `start = i + 1` and reset `balance = 0`.
* At the end, if `total < 0` return -1, else return `start`.
* **🟠Interviewer:**
* You claimed: when balance < 0 at i, none of the regions from start to i can be a valid start. Can you explain that precisely?
* **👩💻Interviewee—證明**
* Let $S(a,b)$ be the sum of diff from $a$ to $b$ inclusive.
* At the moment `balance < 0`, we have: $S(start, i) < 0$.
* Also, because we only reset start when balance first becomes negative, for any $k$ in `(start..i]`, the partial sum up to $k-1$ never went negative: $S(start, k-1) \ge 0$.
* Now consider starting at region $k$ and attempting to reach `i+1`. The net change is:
* $S(k, i) = S(start, i) - S(start, k-1)$
* Since $S(start, i) < 0$ and $S(start, k-1) \ge 0$, we get $S(k, i) < 0$.
* That means starting at $k$ would also run out of tokens before or at $i$, so $k$ cannot be valid. We can safely jump `start` to `i+1`.
* **🟠Interviewer:**
* **🟠Interviewer:**
* Great. Please implement it in C++.
* **👩💻Interviewee—實作**
* I'll use `long long` for `total` and `balance` to avoid overflow.
* (Implemented code as shown in the Solution section above)
* Time Complexity: $O(N)$, Space Complexity: $O(1)$.
* **🟠Interviewer:**
* Looks good. Please walk through the sample.
* **👩💻Interviewee—測試 (Dry Run)**
* Sample: `g = [1,2,3,4,5]`, `c = [3,4,5,1,2]`.
* `diff = [-2, -2, -2, +3, +3]`.
* **Iter 0 (i=0)**: `diff=-2`. `balance=-2`. Since balance < 0, reset `start=1`, `balance=0`.
* **Iter 1 (i=1)**: `diff=-2`. `balance=-2`. Since balance < 0, reset `start=2`, `balance=0`.
* **Iter 2 (i=2)**: `diff=-2`. `balance=-2`. Since balance < 0, reset `start=3`, `balance=0`.
* **Iter 3 (i=3)**: `diff=+3`. `balance=3`. Valid so far.
* **Iter 4 (i=4)**: `diff=+3`. `balance=6`. Valid so far.
* End loop. `total = -2-2-2+3+3 = 0`.
* Since `total >= 0`, return `start = 3`.
* Interpretation: Start the drill at region 3, and the balance never drops below 0.
* **🟠Interviewer:**
* Follow-up: remove the “unique solution” guarantee. Return ALL valid starting region indices. What would you do?
* **👩💻Interviewee—Follow up**
* If solutions may not be unique, the one-pass greedy isn’t enough.
* I’d use **Prefix Sum + Monotonic Queue**.
* Create a linearized array of length `2n` by repeating `diff`.
* Build prefix sums $P$. A starting region $s$ is valid if the sliding window minimum of $P$ in range $(s, s+n]$ is $\ge P[s]$.
* We can maintain a monotonic deque for the minimum prefix value within a window of size $n$.
* This allows us to collect all valid $s$ in $O(N)$ time and $O(N)$ space.
* **🟠Interviewer:**
* Great. That concludes the interview. Thanks!
* **👩💻Interviewee:**
* Thank you for your time.