问题:134. 加油站
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。
分析
这是贪心算法里相当典型的案例。题目要求的是开一周,即每个点都要经过,那么我们需要计算每个点油量的损耗(gas[i] - cost[i])。在此基础上,我们来看暴力的办法:
从每个点开始遍历,走一周,每到一个新的点时,就判断当前的油量是否大于等于0:
- 如果是,则说明可以从出发点到这个节点;
- 如果不是,则说明无法到达;
以上的暴力解法是相当好理解的。如何进行加速呢?思考一个过程,现在有ABC三个点,对应我们的遍历过程,如果从A点出发可以到达B点,即油量有剩余,此时从B点出发无法到C点。此时我们以A为起点的遍历过程就结束了,认为A不是答案,但是我们一定要从B再开始吗?
其实不是,可以直接从C点开始,因为如果从A无法到C,那么A—C之间的节点都不可能是答案。为什么呢?因为假设从A可以到B,说明gas[A]-cost[B]>=0, 那么从A出发到C剩余的油量一定大于等于从B出发到C剩余的油量,如果从A都无法到达,从B出发也一定无法到达。此时的时间复杂度为O(n),空间复杂度为O(n)。
Code
/*
执行结果:
通过
显示详情
执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38.7 MB, 在所有 Java 提交中击败了83.83% 的用户
*/
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int len = gas.length;
int[] remain = new int[len];
// 获取每个节点的油量耗损
for (int i = 0; i < len; i++) remain[i] = gas[i] - cost[i];
for (int i = 0; i < len; i++) {
int sum = 0, step = 0;
while (step < len) {
int idx = (i+step) % len;
sum += remain[idx];
if (sum < 0) {
// 下面这个if判断就是64ms和0ms的区别, 即剪枝过程
if (i < idx) i = idx;
break;
}
step++;
}
if (sum >= 0) return i;
}
return -1;
}
}