问题说明
这是一道来自于 LeetCode No.134 的问题, 问题的定义如下:
在一条环路上有 N N N 个加油站,其中第 i i i 个加油站有汽油 g a s [ i ] gas[i] g a s [ i ] 升。
你有一辆油箱容量无限的的汽车,从第 i i i 个加油站开往第 i + 1 i+1 i + 1 个加油站需要消耗汽油 c o s t [ i ] cost[i] cos t [ i ] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号 ,否则返回 − 1 -1 − 1 。
说明 :
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。
示例 1 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 输入: 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 :
1 2 3 4 5 6 7 8 9 10 11 12 13 输入: 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 升汽油。 因此,无论怎样,你都不可能绕环路行驶一周。
编写测试程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import unittestclass SolutionTest (unittest.TestCase): @classmethod def setUpClass (cls ): cls._func = Solution().canCompleteCircuit def test_1 (self ): args = [[1 , 2 , 3 , 4 , 5 ], [3 , 4 , 5 , 1 , 2 ]] ans = 3 cur_ans = self._func(*args) self.assertEqual(cur_ans, ans) def test_2 (self ): args = [[2 , 3 , 4 ], [3 , 3 , 4 ]] ans = -1 cur_ans = self._func(*args) self.assertEqual(cur_ans, ans) if __name__ == "__main__" : unittest.main(verbosity=2 )
解法 1 - 暴力求解
首先,先尝试暴力法。简单地说,既然问题最终的结果是出发点的索引,那么对于每一个加油站,都将其作为出发点进行尝试,如果能够实现返程,那么此加油站即为答案。如果所有加油站都不能实现返程,那么便返回 -1
作为结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution (object ): def canCompleteCircuit (self, gas, cost ): """ :type gas: List[int] :type cost: List[int] :rtype: int """ n = len (gas) for start in range (n): curr_tank = 0 i = 0 while i < n and curr_tank >= 0 : curr_station = (start + i) % n curr_tank += gas[curr_station] - cost[curr_station] i += 1 if i == n and curr_tank >= 0 : return start return -1
很明显,这种解法的时间复杂度为 O ( N 2 ) O(N^2) O ( N 2 ) ,这样的算法效率自然不能够满足要求。
解法 2 - 贪心策略
现在的问题是,对于这个问题,能否有一种一次遍历的方式求解呢,也就是说算法的时间复杂度需要控制在 O ( N ) O(N) O ( N ) 。
答案当然是有的,下面我们来慢慢分析。
问题分析
能否跑完全程
首先,对于这个问题,至少可以明确的一点是,如果 gas
的元素之和小于 cost
的元素之和,那么肯定不存在这样的行驶路线。
也就是说如果总的汽油量都填补不了总的消耗量,那么无论如何也跑不完全程。在这种情况下算法一定会返回 -1
。
推论 1 : 若 sum(gas) < sum(cost)
,则结果为 -1
但是当 sum(gas) >= sum(cost)
时,该怎么确定呢?
为了再次简化问题,我们将 gas
和 cost
进行合并, 得到 res
,其中: res[i] = gas[i] - cost[i]
也即:res
表示的是经过每个站所需要的净汽油量,正负皆可。则有:
推论 2 : 若 sum(res) < 0
,则结果为 -1
能否作为起点
另外,由于出发时汽油量为 0,因此,如果在某个站,使 gas[i] < cost[i]
,也即 res[i] < 0
,则此点一定不是起点。
推论 3 : 若 res[i] < 0
,则结果不为 i
同时,如果连续的加油站的净汽油量都大于 0,那么最终的答案只可能在这一串加油站的第一个位置(由于答案唯一),我们也有
若 res[i-1] > 0, res[i] > 0
,由于答案唯一,则结果不为 i
如何选择起点
我们先来看一个 sum(res) == 0
的例子:
图中曲线表示的是 res 数组的前缀和,也可以认为是车内剩余的汽油量
参考链接