news 2026/4/17 23:32:29

算法题 K 站中转内最便宜的航班

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法题 K 站中转内最便宜的航班

K 站中转内最便宜的航班

问题描述

n个城市,编号从0n - 1。给你一个航班数组flights,其中flights[i] = [from_i, to_i, price_i]表示从城市from_i到城市to_i的航班价格为price_i

给你三个整数src(出发城市)、dst(目的地城市)和k(最多中转次数),返回从srcdst最多经过 k 次中转的最便宜价格。如果没有这样的路线,返回-1

注意:中转次数 = 航班次数 - 1。例如,直接飞行(0次中转)使用1个航班。

示例

输入: n = 4, flights = [[0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200]], src = 0, dst = 3, k = 1 输出: 700 解释: 在最多 1 次中转的情况下,最便宜的路线是 0->1->3,总费用为 100 + 600 = 700。

算法思路

带限制条件的最短路径问题,不能直接使用 Dijkstra 算法,Dijkstra 无法处理边数限制。

核心

  1. 中转次数:最多 k 次中转意味着最多使用 k+1 条边
  2. 动态规划:可以按边数(或中转次数)进行状态转移

方法

  • Bellman-Ford 算法:天然支持边数限制
  • 动态规划dp[stops][city]表示经过 stops 次中转到达 city 的最小费用
  • BFS + 剪枝:按层数遍历,效率低

Bellman-Ford 核心思想

  1. 初始化:起点费用为0,其他为无穷大
  2. 迭代 k+1 次(最多 k+1 条边)
  3. 每次迭代中,基于上一轮的结果更新当前轮的最短距离
  4. 使用临时数组避免在同一轮中多次更新

代码实现

方法一:Bellman-Ford 算法

importjava.util.*;classSolution{/** * 使用Bellman-Ford算法解K站中转内最便宜的航班 * * @param n 城市数量 * @param flights 航班信息数组,每个元素为[出发城市, 到达城市, 价格] * @param src 出发城市 * @param dst 目的地城市 * @param k 最多中转次数 * @return 最便宜价格,如果不可达返回-1 */publicintfindCheapestPrice(intn,int[][]flights,intsrc,intdst,intk){// 初始化距离数组,dist[i]表示到达城市i的最小费用int[]dist=newint[n];Arrays.fill(dist,Integer.MAX_VALUE);dist[src]=0;// Bellman-Ford算法:最多进行k+1轮松弛操作// 最多k次中转,所以最多k+1条边for(intstops=0;stops<=k;stops++){// 创建临时数组,避免在同一轮中使用更新后的值int[]tempDist=Arrays.copyOf(dist,n);booleanupdated=false;// 遍历所有航班for(int[]flight:flights){intfrom=flight[0];intto=flight[1];intprice=flight[2];// 如果from城市在上一轮中可达,尝试更新to城市的费用if(dist[from]!=Integer.MAX_VALUE){intnewCost=dist[from]+price;if(newCost<tempDist[to]){tempDist[to]=newCost;updated=true;}}}// 如果本轮没有更新,提前结束if(!updated){break;}// 更新距离数组dist=tempDist;}// 返回结果returndist[dst]==Integer.MAX_VALUE?-1:dist[dst];}}

方法二:动态规划

classSolution{/** * 使用动态规划解K站中转内最便宜的航班 * * @param n 城市数量 * @param flights 航班信息 * @param src 出发城市 * @param dst 目的地城市 * @param k 最多中转次数 * @return 最便宜价格 */publicintfindCheapestPrice(intn,int[][]flights,intsrc,intdst,intk){// dp[i][j] 表示经过最多i次中转到达城市j的最小费用// i的范围是0到k+1(0次中转到k+1次中转)int[][]dp=newint[k+2][n];// 初始化:所有费用设为无穷大for(inti=0;i<=k+1;i++){Arrays.fill(dp[i],Integer.MAX_VALUE);}// 起点费用为0(0次中转到达起点)for(inti=0;i<=k+1;i++){dp[i][src]=0;}// 动态规划填表for(intstops=1;stops<=k+1;stops++){// 继承上一轮的结果(不使用新的航班)for(intcity=0;city<n;city++){dp[stops][city]=dp[stops-1][city];}// 尝试使用所有航班进行更新for(int[]flight:flights){intfrom=flight[0];intto=flight[1];intprice=flight[2];// 如果from城市在stops-1次中转内可达if(dp[stops-1][from]!=Integer.MAX_VALUE){intnewCost=dp[stops-1][from]+price;dp[stops][to]=Math.min(dp[stops][to],newCost);}}}// 返回最多k次中转(即k+1条边)的结果returndp[k+1][dst]==Integer.MAX_VALUE?-1:dp[k+1][dst];}}

方法三:Dijkstra 变种(带状态)

importjava.util.*;classSolution{/** * 使用Dijkstra算法的变种,状态包含(城市, 中转次数) */publicintfindCheapestPrice(intn,int[][]flights,intsrc,intdst,intk){// 构建邻接表List<int[]>[]graph=newList[n];for(inti=0;i<n;i++){graph[i]=newArrayList<>();}for(int[]flight:flights){graph[flight[0]].add(newint[]{flight[1],flight[2]});}// 优先队列:[费用, 城市, 中转次数]PriorityQueue<int[]>pq=newPriorityQueue<>((a,b)->a[0]-b[0]);pq.offer(newint[]{0,src,0});// 记录到达每个城市在不同中转次数下的最小费用int[][]minCost=newint[n][k+2];for(inti=0;i<n;i++){Arrays.fill(minCost[i],Integer.MAX_VALUE);}minCost[src][0]=0;while(!pq.isEmpty()){int[]current=pq.poll();intcost=current[0];intcity=current[1];intstops=current[2];// 如果到达目的地,返回费用if(city==dst){returncost;}// 如果中转次数已达到上限,不能继续if(stops>k){continue;}// 遍历邻居for(int[]neighbor:graph[city]){intnextCity=neighbor[0];intprice=neighbor[1];intnewCost=cost+price;intnewStops=stops+1;// 如果找到更优的路径if(newCost<minCost[nextCity][newStops]){minCost[nextCity][newStops]=newCost;pq.offer(newint[]{newCost,nextCity,newStops});}}}return-1;}}

算法分析

  • 时间复杂度

    • Bellman-Ford:O(k × E),其中E是航班数量
    • 动态规划:O(k × E)
    • Dijkstra变种:O(E × k × log(V × k))
  • 空间复杂度

    • Bellman-Ford:O(V)
    • 动态规划:O(V)
    • Dijkstra变种:O(V × k)

算法过程

n = 4, flights = [[0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200]], src = 0, dst = 3, k = 1

Bellman-Ford

初始化

  • dist = [0, ∞, ∞, ∞]

第1轮(0次中转,1条边)

  • 处理航班[0,1,100]:dist[1] = min(∞, 0+100) = 100
  • 其他航班的from城市不可达
  • dist = [0, 100, ∞, ∞]

第2轮(1次中转,2条边)

  • 基于第1轮的dist = [0, 100, ∞, ∞]
  • 处理航班[1,3,600]:dist[3] = 100 + 600 = 700
  • 处理航班[1,2,100]:dist[2] = 100 + 100 = 200
  • 航班[2,3,200]不能使用,因为第1轮中dist[2] = ∞
  • 所以dist[3] = 700

测试用例

publicstaticvoidmain(String[]args){Solutionsolution=newSolution();// 测试用例1:标准示例int[][]flights1={{0,1,100},{1,2,100},{2,0,100},{1,3,600},{2,3,200}};System.out.println("Test 1: "+solution.findCheapestPrice(4,flights1,0,3,1));// 700// 测试用例2:无法到达int[][]flights2={{0,1,100},{1,2,100}};System.out.println("Test 2: "+solution.findCheapestPrice(3,flights2,0,2,0));// -1 (需要1次中转,但k=0)// 测试用例3:直接可达int[][]flights3={{0,1,100}};System.out.println("Test 3: "+solution.findCheapestPrice(2,flights3,0,1,0));// 100// 测试用例4:多条路径int[][]flights4={{0,1,100},{0,2,500},{1,2,100}};System.out.println("Test 4: "+solution.findCheapestPrice(3,flights4,0,2,1));// 200// 测试用例5:中转次数为0int[][]flights5={{0,1,100},{1,2,100},{0,2,500}};System.out.println("Test 5: "+solution.findCheapestPrice(3,flights5,0,2,0));// 500// 测试用例6:大价格值int[][]flights6={{0,1,1000000},{1,2,1000000}};System.out.println("Test 6: "+solution.findCheapestPrice(3,flights6,0,2,1));// 2000000// 测试用例7:起点等于终点int[][]flights7={{0,1,100}};System.out.println("Test 7: "+solution.findCheapestPrice(2,flights7,0,0,0));// 0// 测试用例8:复杂的多路径int[][]flights8={{0,1,1},{0,2,5},{1,2,1},{2,3,1}};System.out.println("Test 8: "+solution.findCheapestPrice(4,flights8,0,3,1));// -1 (需要2次中转)System.out.println("Test 9: "+solution.findCheapestPrice(4,flights8,0,3,2));// 3 (0->1->2->3)// 测试用例10:环路情况int[][]flights10={{0,1,100},{1,2,100},{2,0,100},{1,3,600}};System.out.println("Test 10: "+solution.findCheapestPrice(4,flights10,0,3,1));// 700}

关键点

  1. 中转次数

    • k次中转 = k+1条边
    • Bellman-Ford需要执行k+1轮
  2. 临时数组

    • 使用临时数组避免在同一轮中多次更新
    • Bellman-Ford算法的核心
  3. 边界情况处理

    • 起点等于终点:费用为0
    • 无法到达:返回-1
    • 直接可达:1条边,0次中转

常见问题

  1. 为什么不能用Dijkstra算法?

    • Dijkstra假设一旦找到最短路径就不会被更新
    • 这里路径可能更长但中转次数更少,需要考虑所有可能性
  2. 临时数组?

    • 确保每轮操作只基于上一轮的结果
    • 避免在同一轮中使用刚更新的值,导致错误的多步更新
  3. 如何处理负权边?

    • Bellman-Ford天然支持负权边
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 7:39:35

SMDJ51A单向 TVS瞬态抑制二极管:3000W功率中压浪涌防护核心

SMDJ51A单向 TVS瞬态抑制二极管 二极管产品已经跟我们的生活有着密不可分的联系了&#xff0c; TVS瞬态抑制二极管&#xff0c;是一种高效能保护二极管&#xff0c;产品体积小、功率大、响应快等诸多优点&#xff0c;产品应用广泛 TVS瞬态抑制二极管SMDJ51A&#xff0c;是一种二…

作者头像 李华
网站建设 2026/4/17 12:45:28

小白也能懂:Ubuntu安装Nvidia显卡驱动图解教程

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 制作一个面向新手的交互式Nvidia驱动安装教程。要求&#xff1a;1. 图形化界面展示每个步骤 2. 包含常见错误截图及解决方法 3. 终端命令可直接复制粘贴 4. 安装后基础检测方法。输…

作者头像 李华
网站建设 2026/4/18 8:18:09

小程序毕设选题推荐:基于springboot+微信小程序的校园活动管理系统设计与实现基于微信小程序的大学生社团活动管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/18 8:05:32

传统vs现代:DDoS防护效率对比分析

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个DDoS防护效率对比工具&#xff0c;能够模拟传统规则匹配和现代AI算法两种防护方式&#xff0c;实时展示两者的检测准确率、响应时间和系统资源占用情况。工具应提供可视化对…

作者头像 李华
网站建设 2026/4/18 7:52:57

27、Linux文件与目录管理技术详解

Linux文件与目录管理技术详解 1. 特殊设备介绍 在Linux系统中,存在一些特殊的设备,它们在文件和目录管理中有着独特的用途。 - 零设备(Zero Device) :零设备位于 /dev/zero ,主设备号为1,次设备号为5。与空设备类似,内核会默默地丢弃对零设备的写入操作。当从该…

作者头像 李华
网站建设 2026/4/18 10:28:27

15分钟搭建SM2加密API服务

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 构建一个RESTful API服务&#xff1a;1. /generate-key 生成密钥对 2. /encrypt 接收明文返回密文 3. /decrypt 接收密文返回明文 4. 添加Swagger文档。使用FastAPI框架&#xff0c…

作者头像 李华