题目描述

✅ 134. 加油站

image-20260928203838061

image-20260928203838062

题意分析

环形道路上有若干加油站。到达第 i 站可获得 gas[i] 的油,从这里开到下一站需要消耗 cost[i];最后一站的下一站是第零站。油箱容量没有上限,但出发前是空的。

选择一个起点,先在该站加油,再按顺序行驶,要求整个过程中油量不为负,并最终返回起点。返回起点下标;无法绕行一圈时返回 -1。总油量足够只是必要条件,还要保证选定起点不会在中途先缺油。

解法:贪心跳过失败区间

核心思路

[!blue]

令每站净收益为 diff = gas[i] - cost[i]。累加一段净收益,就是从该段起点空箱出发、依次加油并驶离各站后的剩余油量。total 保存整圈净收益,tank 保存当前候选起点 start 到当前站的净收益。

若 tank 在站点 i 第一次变为负数,说明从 start 无法驶过这里,并且 [start, i] 内的每个位置都不能作为起点。对其中任意 j,从 start 到 j - 1 的净收益此前非负;从 j 到 i 的和等于已经为负的整段和,再减去这段非负前缀,只会更小。因此可以一次跳过整个失败区间,令 start = i + 1 并重置 tank。

为什么最后一个候选还能绕回开头?设 prefix[t] 是前 t 站净收益之和,prefix[0] = 0。只有当前累计值比候选之前的前缀和更低时,才会发生失败并更换起点,所以最终 start 位于一个全局最小前缀和之后。

从这个起点向后行驶,油量是 prefix[t] - prefix[start],不会为负。越过末尾再走到开头部分时,油量是 total + prefix[t] - prefix[start];只要 total >= 0,同样不会为负。因此总收益非负时,最终候选能完成整圈;总收益为负时,任何起点都无法补足整圈消耗。

解题步骤

  1. 初始化全局收益 total = 0、候选收益 tank = 0、起点 start = 0。
  2. 逐站计算 gas[i] - cost[i],同时加入 total 和 tank。
  3. tank < 0 时,排除当前候选到 i 的所有起点,将 start 设为 i + 1,只把 tank 清零。
  4. 扫描结束后,总收益为负则返回 -1,否则返回 start。

代码实现

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int total = 0;
        int tank = 0;
        int start = 0;

        for (int i = 0; i < gas.length; i++) {
            int diff = gas[i] - cost[i];

            total += diff;
            tank += diff;

            if (tank < 0) {
                // 当前起点到 i 之间的所有位置都无法作为起点。
                start = i + 1;
                tank = 0;
            }
        }

        return total >= 0 ? start : -1;
    }
}
func canCompleteCircuit(gas []int, cost []int) int {
    total := 0
    tank := 0
    start := 0

    for i := 0; i < len(gas); i++ {
        diff := gas[i] - cost[i]
        total += diff
        tank += diff
        if tank < 0 {
            // 失败区间整体跳过,下一个位置才可能成为新起点。
            start = i + 1
            tank = 0
        }
    }
    if total >= 0 {
        return start
    }
    return -1
}

复杂度分析

  • 时间复杂度:$O(n)$,失败时一次排除整段候选,每个站点只扫描一次,不需要真的绕行第二圈。
  • 空间复杂度:$O(1)$,只维护候选起点、局部与全局收益;证明中的前缀数组无需实际创建。

关键点总结

[!green]

  • 局部首次失败能排除整个候选区间,是一次扫描的依据。
  • 最小前缀和后的起点能保证向后不缺油,总收益非负则保证绕回部分也不缺油。
  • tank 随候选重置,total 始终保存整圈信息,二者职责不同。
  • 恰好剩零油仍能合法到达下一站,只有负数才代表失败。

易错点总结

[!yellow]

  • 只检查最后一段 tank 非负,就宣称整圈可行,遗漏了此前失败区间留下的总亏损。
  • 更换候选时也把 total 清零,失去判断整圈是否有解的依据。
  • 把新起点设为 i,仍然保留了已经证明不可能的站点;下一候选应为 i + 1。
  • 用 tank <= 0 排除候选,会把恰好用完油的合法状态也判为失败。
  • 只证明候选能走到数组末尾,没有解释环路返回开头的部分;还需要总收益与最小前缀的论证。

相似题目

题目 难度 关联与区别
918. 环形子数组的最大和 中等 两题都可丢弃累计和为负的前缀:本题据此排除一段失败起点,环形最大子数组中的 Kadane 状态也会从更优的新起点重新累计。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/82572488
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!