题目
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i]升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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升汽油。因此,无论怎样,你都不可能绕环路行驶一周。
思路
- 暴力方法:
遍历每个加油站为起点的情况,模拟一圈;如果跑了一圈,中途没有断油,而且最后油量大于等于0,说明这个七点是ok的
for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost){
for(int i = 0; i < cost.length; i++){
int res = gas[i] - cost[i];// 记录剩余油量
int index = ( i + 1 ) % cost.lenghth;
while(res > 0 && index != i){// 模拟以i为起点行驶一圈
res += gas[index] - cost[index];
index = (index + 1) % cost.length;
}
// 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
if(res >= 0 && index == i){
return i;
}
}
return -1;
}
}
- 贪心策略:
贪心一(伪贪心,因为是直接从全局最优考虑问题):
直接从局部进行贪心:
- 如果
gas
的总和小于cost
总和,那么无论从哪里出发,一定是跑不了一圈的 rest[i] = gas[i]-cost[i]
为一天剩下的油,i
从0
开始计算累加到最后一站,如果累加没有出现负数,说明从0
出发,油就没有断过,那么0
就是起点- 如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int sum = 0;
int min = 0;
for (int i = 0; i < gas.length; i++) {
sum += (gas[i] - cost[i]);
min = Math.min(sum, min);
}
if (sum < 0) return -1;
if (min >= 0) return 0;
for (int i = gas.length - 1; i > 0; i--) {
min += (gas[i] - cost[i]);
if (min >= 0) return i;
}
return -1;
}
}
贪心二(全局最优是通过局部最优推导出来的):
如果总油量减去总消耗大于等于0
,那么一定可以跑完一圈,说明各个站点的加油站剩余量res[i]
相加一定是大于等于0
每个加油站的剩余量res[i] = gas[i] - cost[i]
i
从0
开始累加res[i]
,和标记为curSum
,一旦curSum < 0
,说明[0,i]
区间内都不能作为起始位置,起始位置从i+1
算起,curSum
从0
开始计算
上图相当于下标0
的res
为负数,所以从下标1
开始计算,然后下标为1
的剩余res
仍然是负数,继续后移
局部最优 :当前累加res[i]
的和curSum
一旦小于0
,起始位置至少是要从i+1
开始,
全局最优 :找到可以跑完一圈的位置
class Solution {
public int canCompleteCircuit(int[] gas,int[] cost){
int curSum = 0;//记录局部剩余油量,因为有负数的话需要重新i+1的位置开始统计,并且清空累加和
int totalSum = 0;//记录全局剩余油量,看看能不能跑完一圈
int index = 0;
for(int i = 0; i < gas.length; i++){
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if(curSum < 0){
index = (i+1) % gas.length;//从i+1的位置继续累计
curSum = 0;
}
}
if(totalSum < 0){
return -1;
}
return index;
}
}
本题给出了两种贪心算法,对于第一种贪心方法,其实认为就是一种直接从全局选取最优的模拟操作;
对于第二种贪心方法,才真正体现出贪心的精髓,用局部最优可以推出全局最优,进而求得起始位置。