全栈开发知识库

14.加油站

题目

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

  • 输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
  • 输出: 3
  • 解释:
    • 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
    • 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
    • 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
    • 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
    • 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
    • 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。

因此,3 可为起始索引。

示例 2:

  • 输入: gas = [2,3,4], cost = [3,4,3]
  • 输出: -1
  • 解释:
    • 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
    • 我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
    • 开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
    • 开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
    • 你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。

因此,无论怎样,你都不可能绕环路行驶一周。

提示:

  • n == gas.length == cost.length
  • 1 <= n <= 105
  • 0 <= gas[i], cost[i] <= 104
  • 输入保证答案唯一。

思路

  • 题目有一点很重要: 如果存在解,则 保证 它是 唯一 的。
  • 能跑一圈的前提有两个:

    • 每一站的油量都能达到下一站
    • 一圈下来剩下的油量是大于等于0的
  • 我们首先检查第 0 个加油站,并试图判断能否环绕一周;如果不能,就从第一个无法到达的加油站开始继续检查。

class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        n = len(gas)
        start = 0
        while start < n:
            cur = 0
            step = 0
            while step < n:
                nex = (start + step) % n
                cur += gas[nex] - cost[nex]
                # 走到这一步的时候当前的油量< 0了。需要更新起始的站,重新从下一站开始来计算了
                if cur < 0:
                    break
                # 走下一步    
                step += 1
            # 走了n步,也就是一圈了
            if step == n:
                return start
            else:
                # 没走了,一圈,从当前已经走的步数继续遍历找
                start = start + step + 1
        return -1
注意: 为什么使用 start += step + 1 而不是 start += 1?

​如果从起点 start 出发,在走了 step 步后失败(无法到达第 step+1 个加油站),那么从 start 到 start+step 之间的任意一个加油站作为新起点都一定会失败。 对于任意k在[start + 1, start + step]:

  • 如果从 k 出发,我们失去了 start → k 这段路的油量积累(由问题性质决定)。
  • 由于 start → k 这段的油量积累 ​必然是非负的​(因为如果这段是负的,那在 k 之前就已经失败了)。
  • 因此,当从 k 开始时,油量比从 start 开始更少,不可能完成接下来的路程。

使用 start += step + 1 是基于数学性质的优化,避免了无效的重复检查。 改为 start += 1 虽然逻辑正确,但会退化为 O(n²) 的暴力解法,在大型数据集上效率极低。

复杂度分析:

  • 时间复杂度:O(N),其中 N 为数组的长度。我们对数组进行了单次遍历。

  • 空间复杂度:O(1)。

方法二

允许油量为负,但是总剩余油量应该大于等于0,否则不存在解的。

存在解的情况下,利用贪心法的思想,找到最低点,它的下一个点出发的话,可以保证前期得到剩余油量最大,所以可以跑完全程。

public int canCompleteCircuit2(int[] gas, int[] cost) {
    // 一圈下来的总剩余油量
    int totalNum = 0;
    // 从某一站开始时每一站剩余的油量
    int curNum = 0;
    int idx = 0;

    for (int i = 0; i < gas.length; i++) {
        curNum += gas[i] - cost[i];
        totalNum += gas[i] - cost[i];
        if (curNum < 0) {
            idx = (i+1) % gas.length;
            curNum = 0;
        }

    }

    if(totalNum < 0) return -1;
    return idx;
}
class Solution {
    fun canCompleteCircuit(gas: IntArray, cost: IntArray): Int {
        var total = 0
        var current = 0
        var index = 0

        for (i in gas.indices) {
            current += gas[i] - cost[i]
            total += gas[i] - cost[i]
            if (current < 0) {
                index = i + 1
                current = 0 // 注意,重新开始算起点了,所以必须要把current重置为0
            }
        }
        if (total < 0) {
            return -1
        }

        return index
    }
}

解释

为什么 idx = i + 1

核心逻辑:如果从 idx 出发,走到第 i 站时油量为负,说明 idx 到 i 之间的任何一站都不能作为起点。

原因:从 idx 出发时油量是 0,途中每一站到达时油量都是非负的(否则上一次就已经重置了)。如果从中间某站 k(idx ≤ k ≤ i)出发,到达第 i 站的油量 = 从 idx 出发到第 i 站的油量 - 从 idx 出发到第 k 站的油量,后半部分 ≥ 0,所以结果只会更差。

因此直接跳过,从 i + 1 重新尝试。

为什么要 % gas.length

只是防止最后一站(i = gas.length - 1)时 i + 1 越界。

实际上 totalNum < 0 已经处理了无解的情况,有解时 idx 不会真的等于 gas.length,所以 % gas.length 只是防御性写法,去掉也不影响正确性。


  • 邮箱 :charon.chui@gmail.com
  • Good Luck!
本页由 Markdown 自动渲染 · 共 628 篇笔记