# 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.