题目描述:
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;
}
};