LeetCode 134. 加油站
📌 题目描述
题目级别:中等
在一条环路上有 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
💡 破题思路:贪心算法 (一次遍历)
如果使用暴力解法,枚举每个节点为起点去模拟跑一圈,时间复杂度是 O(N2)O(N^2)O(N2),会超时。
事实上,我们可以通过推导得出两个极其强大的结论,从而将复杂度降为 O(N)O(N)O(N):
-
总和 >= 0 必有解 :
如果整条环路的总油量
sum(gas)大于等于总消耗sum(cost),那么一定存在一个起点能够跑完一圈。所以我们需要一个变量tolGas来记录全局的净油量,只要遍历结束后tolGas >= 0,我们就放心大胆地返回计算出的起点。 -
局部亏损引发的连坐否决 :
假设我们从起点
i出发,一路开到了节点j,但在准备前往j+1时发现油箱里的油变负数了。这不仅仅意味着
i不能作为起点,更意味着从i到j之间的任何一个节点都不能作为起点!为什么?因为我们从
i能够开到中间的节点k,说明到达k时油箱里多多少少带着点剩余的油。带着剩余的油从k出发最终都倒在了j,如果你清空油箱直接从k重新出发,绝对会死得更早。贪心策略 :一旦在节点
j处curGas < 0,直接将下一次尝试的起点跳跃到j + 1,并将curGas清零重新累加。
💻 C++ 代码实现 (原汁原味作者版)
cpp
class Solution {
public:
int canCompleteCircuit(std::vector<int>& gas, std::vector<int>& cost) {
int n = gas.size();
// 记录从当前起点 st 出发后的局部净油量
int curGas = 0;
// 记录跑完全程的全局净油量
int tolGas = 0;
// 记录潜在的起点
int st = 0;
for (int i = 0; i < n; i ++ )
{
tolGas += gas[i] - cost[i];
curGas += gas[i] - cost[i];
// 一旦局部油量跌破 0,触发"连坐否决"
if (curGas < 0)
{
curGas = 0; // 油箱清空,从下一个站重新开始计算
st = i + 1; // [st, i] 区间内的所有站点全军覆没,新起点只能是 i + 1
}
}
// 只要全局油量 >= 0,最终锁定下来的 st 就绝对是合法的
return tolGas >= 0 ? st : -1;
}
};