134.加油站*

134.加油站

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gascost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

思路

一开始只想到了暴力解法,双循环遍历,o(n^2),runtime error。

题解给出的解法1,需要一定的数学思想,分为三种情况:

  • 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
  • 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
  • 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前不断累加当前加油站剩余量,直到某个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。

这里比较难想到的是第三点,因为min是取累加过程中sum的最小值,那么在可以跑完一圈的情况下,不断相加总有一个点是大于或等于min的,因此从后向前加填平(因为车是从前向后跑,从后向前加如果是正数能保证不会没油)。详见代码。

解2:每个加油站的剩余量rest[i]为gas[i] - cost[i]。

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

解法2思路相对于解法1的思路好理解一些,从点0起一直加每个点剩余油,若到点a出现负数,代表从点0到点a一定会断油,无法达。那么只好从点a+1开始继续尝试是否再次断油,直到从0遍历完整个加油站(若0到b出现断油,那么b到n必定存在不断油的点,所以无需再次回头判断是否能走到a不断油)。

解3

解3类似于解1的优化版本,更容易理解。因为在保证有解的前提下(sum>=0),那么只需找到curSum最小值,那么该点之后的从第一个点的差值往后加均大于0(不然该点不为最小值),那么就可得出,改点后的第一个点必可以填平这个min(这里或许有解代表sum>0,也即该点后有可能还存在可以解的点),但同时根据题目 “如果存在解,则 保证 它是 唯一 的。” 可得知,该点为唯一解,那么可得有解情况下,改点为最终答案。

这个解法在解1基础上进行优化,主要依据的是唯一解特性,若不唯一则不可用。与解2有异曲同工的道理,解2是一直找和为非负的起点,与解3的min点是同一个点。

代码

解1

    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;
    }

解2

    public int canCompleteCircuit(int[] gas, int[] cost) {
        int totalSum=0;
        int res=0;
        int curSum=0;
        int index=0;
        for (int i=0;i<gas.length;i++){
            res=gas[i]-cost[i];
            curSum+=res;
            totalSum+=res;
            if (curSum<0) {
                curSum=0;
                index=i+1;
            }
        }
        if (totalSum<0) return -1;
        return index;
    }

解3

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;
    }
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值