力扣解题-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
解释:
从 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
输入保证答案唯一。
Related Topics
贪心、数组
示例解答
解题思路
核心方法:贪心算法(一次遍历最优解),通过两个关键变量(全局净油量、当前油箱余量),在一次遍历中完成“是否有解”和“确定起点”的判断,时间复杂度O(n)、空间复杂度O(1),是本题的最优解法。
核心原理铺垫(贪心策略的合理性)
加油站问题的两个核心结论(已被数学证明):
- 有解的必要且充分条件:所有加油站的总供油量 ≥ 总耗油量(即
sum(gas) ≥ sum(cost)); - 起点的贪心选择:若从起点
start出发,行驶到i时油箱余量<0,则start到i之间的所有加油站都不能作为有效起点,下一个候选起点为i+1。
核心逻辑拆解
- 变量定义:
totalTank:全局净油量(sum(gas) - sum(cost)),用于判断是否存在解;currentTank:从当前候选起点start出发的油箱实时余量;start:当前假设的有效起点,初始为0;
- 遍历过程:
- 计算当前加油站的净油量
net = gas[i] - cost[i](加油量 - 到下一站的耗油量); - 累加
totalTank(统计全局净油量)和currentTank(更新当前油箱余量); - 若
currentTank < 0:说明从start出发无法到达i+1站,因此:- 候选起点更新为
i+1(start到i都不可能是有效起点); - 重置
currentTank为0(新起点从空油箱开始);
- 候选起点更新为
- 计算当前加油站的净油量
- 结果判断:
- 若
totalTank ≥ 0:存在唯一解,返回start; - 若
totalTank < 0:总油量不足,返回-1。
- 若
具体步骤(以示例1 gas=[1,2,3,4,5],cost=[3,4,5,1,2]为例)
| i | gas[i] | cost[i] | net | totalTank | currentTank | start | 说明 |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 3 | -2 | -2 | -2 | 1 | currentTank<0,起点更新为1 |
| 1 | 2 | 4 | -2 | -4 | -2 | 2 | currentTank<0,起点更新为2 |
| 2 | 3 | 5 | -2 | -6 | -2 | 3 | currentTank<0,起点更新为3 |
| 3 | 4 | 1 | 3 | -3 | 3 | 3 | currentTank≥0,起点不变 |
| 4 | 5 | 2 | 3 | 0 | 6 | 3 | 遍历完成,totalTank=0≥0 |
| 最终返回start=3,与示例结果一致。 |
性能说明
- 时间复杂度:O(n)(仅一次遍历数组,无嵌套循环),可处理n=10⁵的大数据量;
- 空间复杂度:O(1)(仅使用3个变量,无额外数组/集合开销);
- 核心优势:
- 一次遍历完成所有判断,避免暴力法的O(n²)时间复杂度;
- 贪心策略精准跳过无效起点,无需重复验证;
- 符合题目“答案唯一”的约束,无需处理多解情况。
public int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
int totalTank = 0; // 全局净油量(用于判断是否有解)
int currentTank = 0; // 从当前候选起点出发的油箱余量
int start = 0; // 当前假设的起点
for (int i = 0; i < n; i++) {
int net = gas[i] - cost[i];
totalTank += net;
currentTank += net;
// 如果从 start 出发无法到达 i+1
if (currentTank < 0) {
// 那么 start 到 i 之间的所有点都不能作为起点
start = i + 1; // 下一个候选起点
currentTank = 0; // 重置油箱(新起点从空油箱开始)
}
}
// 如果总油量 >= 总消耗,则 start 是唯一解;否则无解
return totalTank >= 0 ? start : -1;
}
拓展解法:暴力验证法(基础思路,仅作对比)
核心方法:逐个验证每个起点,从每个加油站出发模拟绕环路的过程,判断是否能完成一周,逻辑直观但时间复杂度高,仅适合理解问题本质。
代码实现
public int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
// 遍历每个可能的起点
for (int start = 0; start < n; start++) {
int tank = 0;
boolean canComplete = true;
// 模拟绕环路一周
for (int i = 0; i < n; i++) {
int idx = (start + i) % n; // 环形索引
tank += gas[idx] - cost[idx];
if (tank < 0) { // 油量不足,无法到达下一站
canComplete = false;
break;
}
}
if (canComplete) {
return start;
}
}
return -1;
}
性能说明
- 时间复杂度:O(n²)(最坏情况每个起点都要遍历n个加油站),当n=10⁵时会超时;
- 空间复杂度:O(1);
- 适用场景:仅适合n较小的场景,或作为理解贪心策略的对比参考,实际工程中不推荐使用。
总结
- 贪心算法(最优解):O(n)时间+O(1)空间,一次遍历完成判断,利用“总油量足够则必有解”和“无效起点跳过”的贪心策略,是本题的工程首选;
- 暴力验证法:O(n²)时间+O(1)空间,逻辑直观但性能差,仅适合理解问题本质;
- 关键技巧:
- 贪心策略的核心:无效起点的“批量跳过”(start到i都不可能是起点),避免重复验证;
- 有解的核心判断:总净油量≥0是完成环路的必要且充分条件;
- 环形索引处理:暴力法中用
(start + i) % n实现环形遍历(贪心算法无需此操作)。