题目描述:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/44f09080014cd3a40d928cda7d6aad51.png)
思路
首先自然可以想到要选择一个出发点,使汽车能驶过每一个加油站的前提是,到达所有加油站的耗油量之和Σcost [i]]必须小于等于加油量之和Σgas[i]。当汽车行驶到某一处加油站i时,如果此时油量不足到下一站,但是后续的i+2、i+3....i+n、0站却可以顺利驶完,这是我们就需要考虑如何设计一个策略选择合适的出发点,即一个贪心策略。采用暴力解法可以得到答案,只需两层for遍历每个加油站作为初始点,模拟计算行驶过程中耗油量和加油量变化,这里先给出暴力解法:
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;
}
可以看出最坏情况下,时间复杂度达到了O(n ^ 2),其中n为加油站的数量。显然这并不是题目的最优解法。
在最初考虑题目的时候,仅仅考虑到如何选择一个坐标点,能尽可能的多累加油量即使得初始阶段的gas[i]-cost[i]尽可能大,但是忽略了gas[i]-cost[i]的值可能并不是某局部累加油量最大就能保证行驶完全程,可能出现的情况就是最多走到第 i 个加油站,没有足够的油量驶向 i+1 站,但是后续的 i+2 …i+n站会补偿更多的油量让汽车足以驶完全程。显然考虑局部最大值的思路是错误的,但是贪心题目就是考虑最大最小的问题,我们需要更换贪心策略。这里参考了 > 程序员小灰的思路。
不妨把0点作为起始点start,需要使得从这个点开始的净油量gas[i]-cost[i]之和一直大于0才能行驶完全程。 问题转变成找出这样一个起始点start,这里参考小灰的图,用二维坐标图来形象描述加油站点与净油量的关系,净油量即Sum+=gas[i]-cost[i],如图为例
显然,上图将 0 作为起点肯定是不行的,因为sum在变化的过程中小于 0 了,不符合我们「累加和一直大于等于 0」的要求。
那如果 0 不能作为起点,谁可以作为起点呢?我们可以设想,如果把最低点抬高,即把坐标轴平移,那么是不是就存在可能Sum一直大于0的情况?由于题目情景是一个环形数组,最低点左侧的图像可以接到图像的最右侧,所以抬高坐标轴的思路是可行的。如果把这个「最低点」作为起点,就是说将这个点作为坐标轴原点,就相当于把图像「最大限度」向上平移了。
经过平移后图像一定全部在 x 轴以上吗?不一定,因为还有无解的情况:
如果sum(gas[…]) < sum(cost[…]),总油量小于总的消耗,那肯定是没办法环游所有站点的。
综上,我们就可以写出代码:
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int len = gas.length, maxcost = 0, min = 0x3f3f, start = 0;
for (int i = 0;i < len; ++i){
maxcost += gas[i] - cost[i];
if (maxcost < min){
min = maxcost;
start = i;
}
}
if (maxcost >= 0){
int s = (start + 1) % len;
return s;
} else {
return -1;
}
}
}