一、题目描述
1.题目内容
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas 和 cost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。
原题链接:https://leetcode.cn/problems/gas-station
上图描述了“环形”路线的含义,每个节点i表示加油站,其数值表示gas[i];节点之间的曲线表示从加油站i到加油站j之间的油耗,行驶过程必须顺时针进行。以节点1为起点,题目含义就是顺时针绕行,测试是否可以顺利回到起点。
2.样例
二、解决方案
1.分析
很直观的方案,依次测试每个加油站是否可以作为起点即可,但是这道题正向遍历暴力解很容易报超时,因为测试样例中有几个极其离谱的,后面我展示了一个,所以尝试从数组末尾反向遍历,依次尝试当前加油站是否可以作为起点,一旦遇到第一个可作为起点的加油站,就为最优解。
说一下我的想法:如果以任意站点为起点start,我们逐步行驶,只要可以到达起点的前一个站点end,并且此时汽油剩余量curGas>=cost[end],说明可以顺利回到起点。
以样例1为例,看一下我的解决思路:
(1)第一次,假设站点4(指数组索引)为起点,既start=4,车辆剩余油量curGas=g[start]=5:
- 1)剩余油量为curGas=5>cost[start]=2,所以可以到达下一站点0,此时gas[0]=1,故车辆剩余油量为curGas=3=5-2+1;
- 2)剩余油量为curGas=3>=cost[0]=3,所以可到达下一站点1,此时gas[1]=2,故车辆剩余油量为curGas=2=3-3+2;
- 3)此时curGas=2<cost[1]=4,说明剩余油量无法到达下一站,也就是说以站点4为起点是无法完成环形一周的。因此测试前一个站点3是否可以做起点?
(2)第二次,假设站点3(指数组索引)为起点,既start=3,车辆剩余油量curGas=g[start]=4:
- 1)剩余油量为curGas=4>cost[start]=1,所以可以到达下一站点4,此时gas[4]=5,故车辆剩余油量为curGas=8=4-1+5;
- 2)剩余油量为curGas=8>=cost[4]=2,所以可到达下一站点0,此时gas[0]=1,故车辆剩余油量为curGas=7=8-2+1;
- 3)此时curGas=7>cost[0]=3,所以可到达下一站点1,此时gas[1]=2,故车辆剩余油量为curGas=6=7-3+2;
- 4)此时curGas=6>cost[1]=4,所以可到达下一站点2,此时gas[2]=3,故车辆剩余油量为curGas=5=6-4+3,需要注意此时到达了终点end=start-1=2;
- 5)此时curGas=5>=cost[2]=5,所以可顺利回到起点,因此以站点3为起点是合理的,此时停止遍历得最优解。
2.核心代码
class Solution{
public int canCompleteCircuit(int[] gas, int[] cost) {
int n=gas.length;
int i=n-1; // 从后往前测试
int start=-1; // 为了保证没有找到起点返回-1的条件,初始化start=-1
boolean isCan=false; // 记录是否找到起点
while (i>=0){
if(gas[i]<cost[i]){ // 说明油量小于油耗,无法到达下一站,也就无法作为起点
i--;
continue;
}
start=i; // 假设当前加油站为起点
int end=start-1; // 假设起点的前一站为终点
if (start==0){ // 因为是环形路线,第一站的前一站为整个数组的最后一站
end=n-1;
}
int curGas=gas[start]; // 测试以当前起点开始行驶,是否可以环绕一周
while (true){
if (curGas<cost[i]){ // 行驶过程中发现,当前汽油量无法到达下一各加油站
break;
}
if (i==end&&curGas>=cost[i]){ // 如果到达起点的前一个加油站时,发现汽油量足以到达起点,说明找到了合适的起点
isCan=true;
break;
}
curGas=curGas-cost[i%n]+gas[(i+1)%n]; //计算到达下一站后的汽油量(需要加上下一站的油量)
i=(i+1)%n; // 计算下一站
}
if (isCan){ // 一旦找到起点就停止遍历
break;
}
i=start-1; // 说明当前站无法作为起点,则测试前一个站是否可以作为起点
}
return isCan?start:-1;
}
}
说实话这道题能暴力解出来,真的是有些运气的,人都做崩溃了。。下次再研究大佬们的题解吧。
题外话,这种样例是什么神仙想出来的啊,要我老命。。。