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];
  }
}    
复制代码

 

示例演示过程:

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @   ˙鲨鱼辣椒ゝ  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示