Gas station problem


问题说明

这是一道来自于 LeetCode No.134 的问题, 问题的定义如下:

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

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

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 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
# TEST ONLY
import unittest

class 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 # 初始时汽油量为 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: # 跑完 n 个站 且汽油量足够
return start # 得到索引
return -1

很明显,这种解法的时间复杂度为 O(N2)O(N^2),这样的算法效率自然不能够满足要求。

解法 2 - 贪心策略

现在的问题是,对于这个问题,能否有一种一次遍历的方式求解呢,也就是说算法的时间复杂度需要控制在 O(N)O(N)

答案当然是有的,下面我们来慢慢分析。

问题分析

能否跑完全程

首先,对于这个问题,至少可以明确的一点是,如果 gas 的元素之和小于 cost 的元素之和,那么肯定不存在这样的行驶路线。

也就是说如果总的汽油量都填补不了总的消耗量,那么无论如何也跑不完全程。在这种情况下算法一定会返回 -1

推论 1: 若 sum(gas) < sum(cost),则结果为 -1

但是当 sum(gas) >= sum(cost) 时,该怎么确定呢?

为了再次简化问题,我们将 gascost 进行合并, 得到 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 数组的前缀和,也可以认为是车内剩余的汽油量

参考链接

作者

Cheng

发布于

2020-10-21

更新于

2022-08-06

许可协议

评论