题目:
在一条环路上有 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 可为起始索引。
这是一道难度medium的题,最直接粗暴的方式就是暴力破解,从0出发考虑能否到达0,然后依次考虑从1、从2...出发能否到达环绕一周。
public int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
//考虑从每一个点出发
for (int i = 0; i < n; i++) {
int j = i;
int remain = gas[i];
//当前剩余的油能否到达下一个点
while (remain - cost[j] >= 0) {
//减去花费的加上新的点的补给
remain = remain - cost[j] + gas[(j + 1) % n];
j = (j + 1) % n;
//j 回到了 i
if (j == i) {
return i;
}
}
}
//任何点都不可以
return -1;
}
这种暴力破解的方式慢就慢在考虑了很多重复的计算,可以考虑用额外的空间来记录最远到的距离以及相应剩下的油量。
这还有一种更好的优化思路:假设 i 最远能到 j ,i 和 j 之间的节点都不能到达 j+1。为什么呢?
因为假设 i+1 能到达 j+1 ,而此时因为 i 最远能到 j,所以 i 能达到 i+1,又 i+1 能到达 j+1,所以 i 能达到 j+1 ,这与之前的 i 最远能到 j 的结论相冲突,所以 i 和 j 之间的节点都不能到达 j+1,就不用考虑他们之间的节点了,直接从 j+1开始考虑能不能环绕一周,省去了计算也省去了空间。
public int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
for (int i = 0; i < n; i++) {
int j = i;
int remain = gas[i];
while (remain - cost[j] >= 0) {
//减去花费的加上新的点的补给
remain = remain - cost[j] + gas[(j + 1) % n];
j = (j + 1) % n;
//j 回到了 i
if (j == i) {
return i;
}
}
//最远距离绕到了之前,所以 i 后边的都不可能绕一圈了
if (j < i) {
return -1;
}
//i 直接跳到 j,外层 for 循环执行 i++,相当于从 j + 1 开始考虑
i = j;
}
return -1;
}
那还有没有更简洁的算法呢?
我们可以利用题目中 “如果存在解,则 保证 它是 唯一 的” 来解决该问题。首先想要环绕一周,那么最后剩余的油量一定要大于0,其次再找到途中到达时油量最紧缺的节点,即将其作为旅途最后一站,那么下一个节点便是出发的节点。
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int sum = 0;
int min = Integer.MAX_VALUE;
int minIndex = -1;
for(int i = 0; i < gas.length; i++){
sum = sum + gas[i] - cost[i];
if(sum < min && sum < 0){
min = sum;
minIndex = i;
}
}
if(sum < 0) return -1;
return (minIndex + 1 ) % gas.length;
}
}