附上原题:
There are N gas stations along a circular route, where the amount of gas at station i is gas[i].
You have a car with an unlimited gas tank and it costs cost[i]
of gas to travel from station i 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.
刚看题目的时候以为又是一道动态规划题,看完题目后发现并不是。题目已经给出承诺,如果有解,则一定存在唯一解。如果我们按照常规思路,把所有的解都遍历一遍,那么一次需要遍历N个站,总共遍历N次,时间复杂度为O(n^2)。在这道题目中,我们介绍一种叫做“尺取法”的算法技巧。
尺取法通常是指对数组保存一对下标(起点、终点),然后根据实际情况交替推进两个端点直到得出答案的方法,这种操作很像是尺蠖(日本中称为尺取虫)爬行的方式顾得名。
回到我们的问题上,题目中给出了每个加油站的可加油量以及从当前加油站到下一个加油站的代价。如果从某个加油站出发可以绕线路一圈,那么返回该加油站的索引。我们先来将问题做一个转化:将每个站的加油量减去从当前加油站到下一个加油站的代价,即等于当前加油站到下一个加油站净剩余的油量,如果能保证从某个加油站出发到终点的过程中,所有净剩余油量的累加都不小于0,那么该问题有解。用文字描述可能比较抽象,我们直接来看一个比较简单例子:
example:
gas = [2, 4]
cost = [3, 2]
每个汽油站的净剩余油量
remainders = [-1, 2]
若从第一个加油站开始累加,先得到-1,显然不符合要求。
如果从第二个加油站开始,先得到2,再加上-1等于1,每一次的结果都不小于0。因此符合要求。
利用尺取法,我们可以设计出如下算法:
1 begin = end = sum = 0初始化
2 如果end没有到达终点并且sum不小于0,则sum加上下一个加油站的净剩余量,将end加1
3 如果2中无法满足sum不小于0,则将sum减去起点加油站的净剩余量,并且将起点设为从当前起点的下一个加油站。如果2中end到达了终点,则返回begin。
4 如果begin超过了加油站的数量,说明不存在有效的解,返回-1。否则继续执行第2歩。
附上代码:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
//先计算每个加油站的净剩余油量
vector<int> remainders;
for (int i = 0; i < gas.size(); i++) {
remainders.push_back(gas[i] - cost[i]);
}
int stationNums = gas.size();
int sum = 0, begin = 0, end = 0;
while (begin < stationNums) {
while (end < (stationNums + begin) && sum >= 0) {
sum += remainders[end % stationNums]; //因为是环路,这里记得用模运算
end++;
}
if (sum >= 0) {
return begin;
}
sum -= remainders[begin++];
}
return -1;
}
};