Hard
题目描述
汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。
沿途有加油站,每个加油站由 stations[i] = [positioni, fueli] 表示,表示第 i 个加油站位于出发位置东面 positioni 英里处,并且有 fueli 升汽油。
假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。它每行驶 1 英里就会用掉 1 升汽油。当汽车到达加油站时,它可以停下来加油,将所有汽油从加油站转移到汽车中。
为了到达目的地,汽车所必要的最少加油次数是多少?如果无法到达目的地,则返回 -1。
注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。
示例 1:
输入:target = 1, startFuel = 1, stations = []
输出:0
解释:我们可以在不加油的情况下到达目的地。
示例 2:
输入:target = 100, startFuel = 1, stations = [[10,100]]
输出:-1
解释:我们无法到达目的地,甚至无法到达第一个加油站。
示例 3:
输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]
输出:2
解释:我们从 10 升燃料开始。
我们开车到达位置 10,耗费 10 升燃料。我们从 0 升燃料加油到 60 升燃料。
然后,我们从位置 10 开车到位置 60(耗费 50 升燃料),
并从 10 升燃料加油到 50 升燃料。然后我们开车到达目标。
我们在途中进行了 2 次加油停留,所以我们返回 2。
提示:
1 <= target, startFuel <= 10^90 <= stations.length <= 5001 <= positioni < positioni+1 < target1 <= fueli < 10^9
解题思路
这是一道贪心算法的经典题目。核心思想是:当燃料不足时,总是选择之前经过的加油站中燃料最多的那个进行加油。
贪心策略分析:
- 我们尽可能地向前行驶,直到燃料不足以到达下一个加油站或目的地
- 当燃料不足时,从之前经过但未使用的加油站中选择燃料最多的进行"回溯加油"
- 这样能保证用最少的加油次数获得最多的燃料
算法流程:
- 使用最大堆存储经过但未使用的加油站的燃料量
- 模拟行驶过程:
- 将当前能到达的所有加油站加入堆中
- 如果当前燃料无法到达目的地,从堆中取出燃料最多的加油站进行加油
- 重复直到能到达目的地或堆为空
为什么贪心策略是正确的? 因为我们总是在最需要燃料时选择获得最多燃料的加油站,这样能最大化每次加油的效果,从而最小化加油次数。
其他解法:
- 动态规划:定义
dp[i]为加油i次能到达的最远距离,时间复杂度 O(n²) - 本题推荐贪心+堆的解法,时间复杂度更优
代码实现
class Solution {
public:
int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
priority_queue<int> maxHeap; // 存储经过的加油站的燃料量
int fuel = startFuel;
int position = 0;
int refuels = 0;
int i = 0;
while (position + fuel < target) {
// 将当前能到达的所有加油站加入堆
while (i < stations.size() && stations[i][0] <= position + fuel) {
maxHeap.push(stations[i][1]);
i++;
}
// 如果没有可用的加油站,返回-1
if (maxHeap.empty()) {
return -1;
}
// 选择燃料最多的加油站加油
fuel += maxHeap.top();
maxHeap.pop();
refuels++;
}
return refuels;
}
};
class Solution:
def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
import heapq
max_heap = [] # Python的heapq是最小堆,用负数模拟最大堆
fuel = startFuel
position = 0
refuels = 0
i = 0
while position + fuel < target:
# 将当前能到达的所有加油站加入堆
while i < len(stations) and stations[i][0] <= position + fuel:
heapq.heappush(max_heap, -stations[i][1]) # 用负数模拟最大堆
i += 1
# 如果没有可用的加油站,返回-1
if not max_heap:
return -1
# 选择燃料最多的加油站加油
fuel += -heapq.heappop(max_heap)
refuels += 1
return refuels
public class Solution {
public int MinRefuelStops(int target, int startFuel, int[][] stations) {
var maxHeap = new PriorityQueue<int, int>(Comparer<int>.Create((a, b) => b.CompareTo(a)));
int fuel = startFuel;
int position = 0;
int refuels = 0;
int i = 0;
while (position + fuel < target) {
// 将当前能到达的所有加油站加入堆
while (i < stations.Length && stations[i][0] <= position + fuel) {
maxHeap.Enqueue(stations[i][1], stations[i][1]);
i++;
}
// 如果没有可用的加油站,返回-1
if (maxHeap.Count == 0) {
return -1;
}
// 选择燃料最多的加油站加油
fuel += maxHeap.Dequeue();
refuels++;
}
return refuels;
}
}
var minRefuelStops = function(target, startFuel, stations) {
const maxHeap = new MaxPriorityQueue();
let fuel = startFuel;
let position = 0;
let stops = 0;
let i = 0;
while (position + fuel < target) {
while (i < stations.length && stations[i][0] <= position + fuel) {
maxHeap.enqueue(stations[i][1]);
i++;
}
if (maxHeap.isEmpty()) {
return -1;
}
const maxFuel = maxHeap.dequeue().element;
fuel += maxFuel;
stops++;
}
return stops;
};
复杂度分析
| 复杂度 | 分析 |
|---|---|
| 时间复杂度 | O(n log n),其中 n 是加油站数量。每个加油站最多被加入和移出堆一次,堆操作的时间复杂度为 O(log n) |
| 空间复杂度 | O(n),最坏情况下需要将所有加油站都加入堆中 |