K站中转内最便宜的航班

LeetCode 787

K站中转内便宜的航班

有 n 个城市通过 m 个航班连接。每个航班都从城市 u 开始,以价格 w 抵达 v。

现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到从 src 到 dst 最多经过 k 站中转的最便宜的价格。 如果没有这样的路线,则输出 -1。

示例 1:

输入: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
输出: 200
解释: 
城市航班图如下

从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。

示例2:

输入: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
输出: 500
解释: 
城市航班图如下

从城市 0 到城市 2 在 0 站中转以内的最便宜价格是 500,如图中蓝色所示

解法:动态规划

解题思路:

这道题可以基于dijkstra算法来做,求最短路径的同时限制了中转次数,因此我们可以用一个优先队列来存放每一个可以经过的站,结构如下:

PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);

int[] 中存放三个值
1. cost:总共消耗的金钱
2. k:经过中转站的次数
3. 当前所在中转站位置

实现的原理为:

在这里插入图片描述
考虑上面的图,假设出发点是0,终点为3,中转次数为2,这时候最短路径就是0->2->1->3

当我们将0加入优先队列时,0会将通过1和通过2所需的花费和中转次数加入到优先队列中,如下结构

{200,1,2}
{400,1,1}

这时候我们从优先队列取出的就是{200,1,2}这一站,同理,取出这一站后该站会将通过它相邻站所需的花费以及相关信息加入到队列中{cost,k,cur},因此,优先队列可以保证每次从取出的都是最短路径,但是要注意该题还有中转次数的限制,因此我们需要一个map来保存经过相同次中转所需的最少花费

如上图:

0->1->3
0->2->3

这两条路径都是通过相同次数的中转到达目的地的,因此需要一个map来保证取的最小值

完整代码如下:

class Solution {
    public static int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
        int[][] Cost = new int[n][n]; //保存铁路的花费
        for(int[] arr : flights)
            Cost[arr[0]][arr[1]] = arr[2]; //初始化
        //不连通的两个站花费为0
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);//优先队列保存每一个站,以花费排序
        pq.add(new int[] {0,0,src}); //将出发点加入队列
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>(); //保存出发点经过每一个中转站的花费,将k*1000是为了筛选出经过相同次数中转站花费最少的值,乘于1000是为了避免数据干扰
        while(!pq.isEmpty())
        {
            int[] tmp = pq.poll(); //取出一个中转站
            int cost = tmp[0], k = tmp[1], cur = tmp[2];
            if(k>K+1 || cost>map.getOrDefault(k*1000+cur,Integer.MAX_VALUE))
            //如果当前中转次数大于K+1或者花费大于已存在的最小值时放弃当前站
                continue;
            if(cur==dst)
            //当前站位于终点时退出返回结果
                return cost;
            for(int i=0; i<n; i++) //取出当前站的所有相邻点
            {
                if(Cost[cur][i]!=0) //是相邻点
                {
                    int t = cost + Cost[cur][i]; //经过相邻站的花费
                    pq.add(new int[] {t,k+1,i});  //加入队列
                    map.put((k+1)*1000+i,t); //将花费加入map中
                }
            }
        }
        return -1;
    }

如果去掉限制的话,那么我们就不需要map也能实现

代码如下:

public static int findCheapestPrice(int n, int[][] flights, int src, int dst) {
    int[][] Cost = new int[n][n];
    for(int[] arr : flights)
        Cost[arr[0]][arr[1]] = arr[2];
    PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);
    pq.add(new int[] {0,0,src});
    while(!pq.isEmpty())
    {
        int[] tmp = pq.poll();
        int cost = tmp[0], k = tmp[1], cur = tmp[2];
        System.out.printf("%d %d %d\n",cost,k,cur);
        if(cur==dst)
            return cost;
        for(int i=0; i<n; i++)
        {
            if(Cost[cur][i]!=0)
            {
            	int t = cost + Cost[cur][i];
            	pq.add(new int[] {t,k+1,i});   
            }
        }
    }
    return -1;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值