[leetcode] Gas Station

本文探讨了如何使用不同的算法解决循环油站问题,并详细解释了每种方法的复杂度和实现步骤,最终实现了一个O(n)复杂度的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

There are N gas stations along a circular route, where the amount of gas at stationi is gas[i].

You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from stationi to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.

Return the starting gas station's index if you can travel around the circuit once, otherwise return -1.

Note:
The solution is guaranteed to be unique.

1.最直观的解法

  分别选取0~N-1站点作为起始点,计算从该起始点出发能否再回到该起始点,考虑到从p点到q点需要满足方程组:

    gas[p]>=cost[p]

    gas[p]+gas[p+1]>=cost[p]+cost[p+1]

    ....

    gas[p]+gas[p+1]+...+gas[q-1]>=cost[p]+cost[p+1]+...+cost[q-1]

 故可以用两个变量totalgas和totalcost依次累加gas[p]和cost[p] ,  若直到p==q时totalgas仍然不小于totalcost, 则说明从点p出发可以回到p点。  否则不行

 这种方法最直观,但复杂度最高,为平方阶。代码如下:

class Solution {
public:
    
    bool testStartPoint(vector<int> &gas,vector<int> &cost,int startPos,int N)
    {
        int totalgas=0,totalcost=0,curPos;
        for(curPos=startPos;curPos<N;++curPos)
        {
            totalgas+=gas[curPos];
            totalcost+=cost[curPos];
            if(totalgas<totalcost)
                return false;
        }
        for(curPos=0;curPos<startPos;++curPos)
        {
            totalgas+=gas[curPos];
            totalcost+=cost[curPos];
            if(totalgas<totalcost)
                return false;
        }
        return true;
    }


    int canCompleteCircuit(vector<int> &gas, vector<int> &cost) {
        int N=gas.size();
        for(int i=0;i<N;++i)
        {
            if(testStartPoint(gas,cost,i,N))
                return i;
        }
        return -1;
    }
};

 

2.思路变换一

假设要使车走的更远些,则肯定会选择这样的站点为起点:从该站点出发,先遇到的都是那些加油大于耗油的站,以使得车攒够足够的油来度过那些耗油多过加油的站点。如果从该点出发仍不能走一圈,则应返回-1.

对于站点i,我们把gas[i]-cost[i]作为一个整体来考虑,令diff[i]=gas[i]-cost[i],表示从站点i出发到i+1站点后剩余的油量。则要想使车辆走的更远些,应选择可以使diff的累加值达到峰值的点作为起始点。

从而问题转化为求diff数组上和最大的连续子数组。也即求循环数组的和最大连续子数组。由动态规划求可达到线性复杂度。由于是循环数组,可求出和最大连续数组MAX,以及和最小连续数组MIN,  然后取MAX和total-MIN中的较大者即为和最大连续子数组,其中total为数组所有数的和。  由于我们要找的是连续数组的起始点,即为MAX的起始点或者MIN的终止点的下一个点。

代码如下,O(n)

class Solution {
public:
    int canCompleteCircuit(vector<int> &gas, vector<int> &cost) {
        if(gas.size()!=cost.size()) return -1;
        int N=gas.size();
        if(N==0) return -1;
        int MAX,MIN,max,min;
        int total=0;
        int stmax,stMax,endMin;
        MAX=MIN=max=min=gas[0]-cost[0];
        stmax=stMax=endMin=0;
        int diff;
        for(int i=0;i<N;++i)
        {
            diff=gas[i]-cost[i];
            total+=diff;
            
            if(max<0)
            {
                max=diff;
                stmax=i;
            }
            else
            {
                max+=diff;
            }
            if(max>MAX)
            {
                MAX=max;
                stMax=stmax;
            }
            if(min>0)
            {
                min=diff;
            }
            else
            {
                min+=diff;
            }
            if(min<MIN)
            {
                MIN=min;
                endMin=i;
            }
        }
        
        return total<0?-1:(MAX>(total-MIN)?stMax:(endMin+1)%N);
    }
};


3.思路变换二

解法一是依次从每个站点出发测试能否回到原点,这其中有很多次测试是不必要的。

假设从站点i出发,到站点k-1时油箱未空,但到k之前油箱空了,即diff[i]+diff[i+1]+...+diff[k]<0 ,且在从i到k之前的所有diff的累加均不小于0,也即diff[i]>=0,diff[i]+diff[i+1]>=0...

所以若从站点i+1出发,由于diff[i]>=0 ,所以,diff[i+1]+diff[i+2]+...+diff[k]<0,从而车也不能到达k点,i+2,i+3。。。等节点同理。  所以下一次应从k+1节点开始出发测试。

因此将复杂度从O(n^2)降到了O(2n),之所以是2n,是因为从k+1出发的测试需要绕一圈到k才能判断是否满足。

但是,真的需要这样么?

我们可以模拟一下过程:

a. 假设从0出发,开出p点后没油了。则sum1=diff[0]+diff[1]+...+diff[p]<0

b.接着从p+1出发,开出q点后没油了。则sum2=diff[p+1]+diff[p+2]+....diff[q]<0

c.从q+1出发,假设开到未循环的最后一站还有油,则sum3=diff[q+1]+diff[q+2]+...diff[size-1]>0

要想知道能否开回到q点,就是在sum3的基础上依次加上diff[0]到diff[q],看过程中是否会出现小于0 的情况。而由先前的讨论我们已经知道了从diff[0]到diff[p-1]的这一段的累加始终为非负。所有只需计算sum3+sum1,若不小于0则可以开到p点,同理从p到q点也是此思路,最后得出结论即 若sum1+sum2+sum3>=0 则可以走一圈。 而sum1+sum2+sum3即为整个diff数组的累加和。

此时复杂度为O(n),代码如下:

class Solution {
public:
    int canCompleteCircuit(vector<int> &gas, vector<int> &cost) {
        if(gas.size()!=cost.size()) return -1;
        int N=gas.size();
        if(N==0) return -1;
        int sum=0,total=0,start=0;
        for(int i=0;i<N;++i)
        {
            total+=(gas[i]-cost[i]);
            
            if(sum<0)
            {
                sum=gas[i]-cost[i];
                start=i;
            }
            else
                sum+=(gas[i]-cost[i]);
        }
        return total<0?-1:start;
    }
};



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值