871. 最低加油次数
题目描述:
汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。沿途有加油站,每个 station[i] 代表一个加油站,它位于出发位置东面 station[i][0] 英里处,并且有 station[i][1] 升汽油。假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。它每行驶 1 英里就会用掉 1 升汽油。当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。
注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。
提示:
- 1 <= target, startFuel, stations[i][1] <= 10^9
- 0 <= stations.length <= 500
- 0 < stations[0][0] < stations[1][0] < ... < stations[stations.length-1][0] < target
示例:
输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]
输出:2
解题思路:
1.回溯剪枝
一开始想到的是遍历所有的状态空间,根据当前加油站是否加油分为两种情况进行深度优先搜索,如果当前状态空间计算出的加油次数已经超出当前最小加油次数,则进行剪枝,不再往下递归。但这种方法的时间复杂度是O(2n),当后面数据范围变大的时候会造成算法超时。
代码实现:
/** * @param {number} target * @param {number} startFuel * @param {number[][]} stations * @return {number} */ var minRefuelStops = function(target, startFuel, stations) { let destination = []; //计算出每个加油站距离终点还有多远 for(let i=0;i<stations.length;i++){ destination[i] = target - stations[i][0]; } let ans = stations.length+1;//初始化最小加油次数 //如果一开始油量就能到达终点,则无需加油 if(startFuel>=target){ return 0; } //如果一开始油量就到达不了终点且中途无加油站,则无法到达 if(stations.length==0&&startFuel<target){ return -1; } ans = dfs(startFuel,stations,destination,0,0,ans); //表示无法到达终点 if(ans==stations.length+1){ return -1; } return ans; }; /** * @param {number} index ->表示遍历到第index个加油站 * @param {number} count ->记录加油次数 * @param {number} ans ->当前计算出来的最小加油次数 */ function dfs(startFuel,stations,destination,index,count,ans){ if(index>=stations.length||count>=ans){ return stations.length+1; } //consume表示当前加油站到前一个加油站的距离,也即油耗 let consume = 0; if(index<1){ consume = stations[index][0];//第一个加油站到起点的距离 }else{ consume = stations[index][0]- stations[index-1][0]; } //当前油量不足以到达第index个加油站 if(startFuel<consume){ return stations.length+1; } //在当前加油站进行加油 let addFuel = startFuel - consume+stations[index][1]; count++; //如果当前油量足以到达终点,直接返回加油次数 if(addFuel>=destination[index]){ return count; }else { //比较出当前最小的加油次数 ans = Math.min(ans, dfs(addFuel,stations,destination,index+1,count,ans)); } //在当前加油站不加油 let no_addFuel = startFuel - consume; count--; if(no_addFuel>=destination[index]){ return count; }else { ans = Math.min(ans,dfs(no_addFuel,stations,destination,index+1,count,ans)); } return ans; }
示例演示过程:
2.贪心+优先队列
要求计算最少的加油次数,我们应该在路过的加油站中选储油量最多的加油站加油,这样可以保证我们走得更远,因此可以维护一个优先队列来存储路过的加油站。假设我们在路过每个加油站时,只要油量足够到达当前加油站,我们就选择不加油,将该加油站加入优先队列。当油量不足以到达当前加油站时,我们从优先队列中取出储油量最多的加油站,将它的油量添加到当前油量中(相当于有一颗“后悔药”,当发现到不了当前加油站时,我们可以选择在之前路过的加油站中加油),重新判断当前油量是否能到达该加油站(因为加油站是按距离长短排列的,因此当前加油站到达不了的话下一个必然也到达不了)。
可能我们会想到:储油量多的加油站有可能离要到达的加油站或者终点很远,每次选择它来加油是不合理的。这里要注意的是,我们加的油量相当于加里程数,是在我们现在能到达的最远加油站的基础上加里程数的,并不是在所选的加油站的位置上添加里程数的。可以这么理解,我没加油之前都能到这里了,那我在之前加过油的话可能能到更远。
代码中总是以起点开始算里程数的,根据油量来计算出能到达的最远距离,以此来判断能否到达当前位置。
代码实现如下:
/** * @param {number} target * @param {number} startFuel * @param {number[][]} stations * @return {number} */ var minRefuelStops = function(target, startFuel, stations) { let count = 0;//记录加油次数 const heap = new PriorityQueue((a,b)=>b-a); //看当前油量能否到达各个加油站 for(let i=0;i<stations.length;){ //如果油量能到达当前加油站,就选择不加油,将该加油站入列 if(startFuel>=stations[i][0]){ heap.push(stations[i][1]); i++; }else{//当发现不能到达当前加油站时,去队列中找加油站加油 if(heap.data.length==0){//队列为空表示路过的加油站都加过油了,已经无法到达当前加油站 return -1; }else{//选择储油量最大的加油站加油 startFuel += heap.pop(); count++; } } } //看当前油量能否到达终点 while(startFuel<target){ if(heap.data.length>0){ startFuel += heap.pop(); count++; }else{ return -1; } } return count; }; //自定义的优先队列结构,根据传入的比较器来选择最大最小堆 class PriorityQueue { constructor(compare) { if(typeof compare !=='function'){ throw new Error('compare function required!') } this.data = [] this.compare = compare } //二分查找 寻找插入位置 search(target) { let low = 0, high = this.data.length while (low < high) { let mid = low + ((high - low) >> 1) if (this.compare(this.data[mid], target) > 0) { high = mid } else { low = mid + 1 } } return low; } //添加 push(elem) { let index = this.search(elem) this.data.splice(index, 0, elem) return this.data.length } //取出最优元素 pop() { return this.data.shift() } //查看最优元素 peek() { return this.data[0]; } }
示例演示过程:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具