核心思想

Dijkstra算法

Dijkstra 算法:给定一个源点,求解从源点到每个点的最短路径长度。单源最短路径算法。 适用范围:有向图、边的权值没有负数

彻底暴力的 Dijkstra 算法,不讲、时间复杂度太差、无意义

普通堆实现的 Dijkstra 算法,最普遍、最常用 算法核心过程: 节点弹出过就忽略 节点没弹出过,让其它没弹出节点距离变小的记录加入堆

反向索引堆实现的 Dijkstra 算法,最快速、最极致 核心在于掌握反向索引堆

对应本节题目 1、题目 2、题目 3

普通版

普通堆实现的 Dijkstra 算法,时间复杂度 O (m * log m),m 为边数

1、distance[i]表示从源点到 i 点的最短距离,visited[i]表示 i 节点是否从小根堆弹出过

2、准备好小根堆,小根堆存放记录:(x 点,源点到 x 的距离),小根堆根据距离组织

3、令 distance[源点]=0,(源点,0) 进入小根堆

4、从小根堆弹出 (u 点,源点到 u 的距离)

a. 如果 visited[u] == true,不做任何处理,重复步骤 4

b. 如果 visited[u] == false,令 visited[u] = true,u 就算弹出过了 然后考察 u 的每一条边,假设某边去往 v,边权为 w

1)如果 visited[v] == false 并且 distance[u] + w < distance[v]

令 distance[v] = distance[u] + w,把 (v, distance[u] + w) 加入小根堆

2)处理完 u 的每一条边之后,重复步骤 4

5、小根堆为空过程结束,distance 表记录了源点到每个节点的最短距离。

模版代码

 class Solution {  
     public int networkDelayTime(int[][] times, int n, int k) {  
         //首先是建图  
         ArrayList<ArrayList<int[]>> graph=new ArrayList<>();  
         //初始化,注意测试用例给的数据都是从1开始的  
         for(int i=0;i<=n;i++){  
             graph.add(new ArrayList<>());  
         }  
         //添加对应的边  
         for(int[] time:times){  
             graph.get(time[0]).add(new int[]{time[1],time[2]});  
         }  
         //构造距离数组,注意测试用例是从1开始计数,从1到N  
         int[]distance=new int[n+1];  
         for(int i=1;i<=n;i++){  
             distance[i]=Integer.MAX_VALUE;  
         }  
         boolean[] visits=new boolean[n+1];  
         //准备普通的小根堆  
         PriorityQueue<int[]> heap=new PriorityQueue<>((a, b)->a[1]-b[1]);  
         //接下来开始Dijkstra算法  
         //初始化源点  
         distance[k]=0;  
         heap.add(new int[]{k,0});  
         while(!heap.isEmpty()){  
             int u=heap.poll()[0];  
             // 如果 visited[u] == true,不做任何处理,重复步骤 4            
             if(visits[u]){  
                 continue;  
             }  
             //如果 visited[u] == false,令 visited[u] = true,u 就算弹出过了  
             visits[k]=true;  
             //考察u的每一条边  
             for(int[]edge:graph.get(u)){  
                 //u->v  
                 int v=edge[0];  
                 //权重  
                 int w=edge[1];  
                 //如果 visited[v] == false 并且 distance[u] + w < distance[v]                
                 //令 distance[v] = distance[u] + w,把 (v, distance[u] + w) 加入小根堆  
                 if(distance[u]+w<distance[v]){  
                     distance[v]=distance[u]+w;  
                     heap.add(new int[]{v,distance[u]+w});  
                 }  
             }  
         }  
         int ans=0;  
         for(int i=1;i<=n;i++){  
             ans=Math.max(ans,distance[i]);  
         }  
         //若有无法到达的点则distance数组中必有一个代价为Integer.MAX_VALUE的点  
         return ans==Integer.MAX_VALUE?-1:ans;  
     }

优化版

反向索引堆实现的 Dijkstra 算法,时间复杂度 O (m * log n),n 为节点数,m 为边数

1、准备好反向索引堆,根据源点到当前点的距离组织小根堆,可以做到如下操作

a. 新增记录 (x, 源点到 x 的距离)

b. 当源点到 x 的距离更新时,可以进行堆的调整

c. x 点一旦弹出,以后忽略 x

d. 弹出堆顶的记录 (u, 源点到 u 的距离)

2、把 (源点,0) 加入反向索引堆,过程开始

3、反向索引堆弹出 (u,源点到 u 的距离),考察 u 的每一条边,假设某边去往 v,边权为 w

1)如果 v 没有进入过反向索引堆里,新增记录 (v, 源点到 u 的距离 + w)

2)如果 v 曾经从反向索引堆弹出过,忽略

3)如果 v 在反向索引堆里,看看源点到 v 的距离能不能变得更小,如果能,调整堆;不能,忽略

4)处理完 u 的每一条边,重复步骤 3

4 、反向索引堆为空过程结束。反向索引堆里记录了源点到每个节点的最短距离。

模版代码

 //Dijkstra算法优化版本:反向索引堆+链式前向星建图  
 class Solution2 {  
     public static int MAXN=101;  
     public static int MAXM=6001;  
     //链式前向星  
     public static int[]head=new int[MAXN];  
     public static int[]next=new int[MAXM];  
     public static int[]to=new int[MAXM];  
     public static int[]weight=new int[MAXM];  
     public static int cnt;  
     //反向索引堆  
     public static int[]heap=new int[MAXN];  
     // where[v] = -1,表示v这个节点,从来没有进入过堆  
     // where[v] = -2,表示v这个节点,已经弹出过了  
     // where[v] = i(>=0),表示v这个节点,在堆上的i位置  
     public static int[]where=new int[MAXN];  
     public static int heapSize;  
     public static int[]distance=new int[MAXN];  
   
     public static int networkDelayTime(int[][] times, int n, int k) {  
         build(n);  
         //链式前向星建图  
         for(int[]edge:times){  
             addEdge(edge[0],edge[1],edge[2]);  
         }  
         //开始 Dijkstra算法  
         //源点进入  
         addOrUpdateOrIgnore(k,0);  
         while(!isEmpty()){  
             int u=pop();  
             for(int ei=head[u];ei>0;ei=next[ei]){  
                 addOrUpdateOrIgnore(to[ei],distance[u]+weight[ei]);  
             }  
         }  
         int ans=0;  
         for(int i=1;i<=n;i++){  
             ans=Math.max(ans,distance[i]);  
         }  
         return ans==Integer.MAX_VALUE?-1:ans;  
     }  
   
     public static void build(int n){  
         cnt=1;  
         heapSize=0;  
         Arrays.fill(head,0,n+1,0);  
         Arrays.fill(where,0,n+1,-1);  
         Arrays.fill(distance,0,n+1,Integer.MAX_VALUE);  
     }  
     public static void addEdge(int u,int v,int w){  
         next[cnt]=head[u];  
         to[cnt]=v;  
         weight[cnt]=w;  
         head[u]=cnt++;  
     }  
     //反向堆索引优化  
     /**  
      3,反向索引堆弹出 (u,源点到 u 的距离),考察 u 的每一条边,假设某边去往 v,边权为 w  
      1)如果 v 没有进入过反向索引堆里,新增记录 (v, 源点到 u 的距离 + w)  
      2)如果 v 曾经从反向索引堆弹出过,忽略  
      3)如果 v 在反向索引堆里,看看源点到 v 的距离能不能变得更小,如果能,调整堆;不能,忽略  
      4)处理完 u 的每一条边,重复步骤 3  
      4 反向索引堆为空过程结束。反向索引堆里记录了源点到每个节点的最短距离。  
   
      */  
     public static void addOrUpdateOrIgnore(int v,int c){  
         if(where[v]==-1){  
             //将点加入堆中  
             heap[heapSize]=v;  
             where[v]=heapSize++;  
             distance[v]=c;  
             //新加入的点要网上看,使堆为小根堆  
             heapInsert(where[v]);  
         }else if(where[v]>=0){  
             distance[v]=Math.min(distance[v],c);  
             heapInsert(where[v]);  
         }  
     }  
   
     public static void heapInsert(int i){  
         while(distance[heap[i]]<distance[heap[(i-1)/2]]){  
             swap(i,(i-1)/2);  
             i=(i-1)/2;  
         }  
     }  
   
     public static boolean isEmpty(){  
         return heapSize==0;  
     }  
     public static int pop(){  
         int ans=heap[0];  
         //要复原小根堆  
         swap(0,--heapSize);  
         heapify(0);  
         //弹出后的点标记,后续有关这个点的所有操作都不再处理  
         where[ans]=-2;  
         return ans;  
     }  
   
     public static void heapify(int i){  
         int l=i*2+1;  
         while(l<heapSize){  
             int best=l+1<heapSize && distance[heap[l+1]]<distance[heap[l]]?l+1:l;  
             best=distance[heap[best]]<distance[heap[i]]?best:i;  
             if(best==i){  
                 break;  
             }  
             swap(i,best);  
             i=best;  
             l=2*i+1;  
         }  
     }  
   
     public static void swap(int i,int j){  
         //首先是堆中交换  
         int tmp=heap[i];  
         heap[i]=heap[j];  
         heap[j]=tmp;  
         //不要忘记索引数组的交换  
         where[heap[i]]=i;  
         where[heap[j]]=j;  
     }  
 }

分层图最短路

分层图最短路,又叫扩点最短路

不把实际位置看做图上的点,而是把实际位置及其状态的组合看做是图上的点,然后搜索 Bfs 或者 Dijkstra 的过程不变,只是扩了点(分层)而已 原理简单,核心在于如何扩点、如何到达、如何算距离,每个题可能都不一样

对应本节题目 4、题目 5、题目 6

例题

1、网络延迟时间

题目描述: 有 n 个网络节点,标记为 1 到 n

给你一个列表 times,表示信号经过 有向 边的传递时间。 times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点, wi 是一个信号从源节点传递到目标节点的时间。

现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1 。

示例 1:

输入:times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2 输出:2

示例 2:

输入:times = [[1,2,1]], n = 2, k = 1 输出:1

示例 3:

输入:times = [[1,2,1]], n = 2, k = 2 输出:-1

提示:

  • 1 <= k <= n <= 100

  • 1 <= times.length <= 6000

  • times[i].length == 3

  • 1 <= ui, vi <= n

  • ui != vi

  • 0 <= wi <= 100

  • 所有 (ui, vi) 对都 互不相同(即,不含重复边)

项目名称

内容填写(每道题一行填写)

📘 题目编号 / 标题

LeetCode 743 + 网路延迟问题

🧠 题目关键词(原文关键词)

有向图,需要多久才能使所有节点都收到信号

🧩 抽象问题类型(题目本质)

源点到每个点的最短路径问题

🔍 数据规模 / 限制

- 1 <= k <= n <= 100 - 1 <= times.length <= 6000 - times[i].length == 3 - 1 <= ui, vi <= n - ui != vi - 0 <= wi <= 100 - 所有 (ui, vi) 对都 互不相同(即,不含重复边)

🧭 我的初步思路

求最短路径

✅ 正确解法类型

Dijkstra

❗ 没想到的原因

不会分析抽象,没再题目中明显看到最短路径的要求

📦 归入的题型分类

Dijkstra 算法

🧠 触发词(以后遇到就联想)

网络延时问题-->Dijkstra 算法

🧪 解法一句话总结

Dijkstra 算法找出源点到每个点的最短时间距离,然后找出其中的最大值

Dijkstra 算法普通版:

 class Solution {  
     public int networkDelayTime(int[][] times, int n, int k) {  
         //首先是建图  
         ArrayList<ArrayList<int[]>> graph=new ArrayList<>();  
         //初始化,注意测试用例给的数据都是从1开始的  
         for(int i=0;i<=n;i++){  
             graph.add(new ArrayList<>());  
         }  
         //添加对应的边  
         for(int[] time:times){  
             graph.get(time[0]).add(new int[]{time[1],time[2]});  
         }  
         //构造距离数组,注意测试用例是从1开始计数,从1到N  
         int[]distance=new int[n+1];  
         for(int i=1;i<=n;i++){  
             distance[i]=Integer.MAX_VALUE;  
         }  
         boolean[] visits=new boolean[n+1];  
         //准备普通的小根堆  
         PriorityQueue<int[]> heap=new PriorityQueue<>((a, b)->a[1]-b[1]);  
         //接下来开始Dijkstra算法  
         //初始化源点  
         distance[k]=0;  
         heap.add(new int[]{k,0});  
         while(!heap.isEmpty()){  
             int u=heap.poll()[0];  
             // 如果 visited[u] == true,不做任何处理,重复步骤 4                  if(visits[u]){  
                 continue;  
             }  
             //如果 visited[u] == false,令 visited[u] = true,u 就算弹出过了  
             visits[k]=true;  
             //考察u的每一条边  
             for(int[]edge:graph.get(u)){  
                 //u->v  
                 int v=edge[0];  
                 //权重  
                 int w=edge[1];  
                 //如果 visited[v] == false 并且 distance[u] + w < distance[v]                //令 distance[v] = distance[u] + w,把 (v, distance[u] + w) 加入小根堆  
                 if(distance[u]+w<distance[v]){  
                     distance[v]=distance[u]+w;  
                     heap.add(new int[]{v,distance[u]+w});  
                 }  
             }  
         }  
         int ans=0;  
         for(int i=1;i<=n;i++){  
             ans=Math.max(ans,distance[i]);  
         }  
         //若有无法到达的点则distance数组中必有一个代价为Integer.MAX_VALUE的点  
         return ans==Integer.MAX_VALUE?-1:ans;  
     }

Dijkstra 算法优化版

   //Dijkstra算法优化版本:反向索引堆+链式前向星建图  
 class Solution2 {  
     public static int MAXN=101;  
     public static int MAXM=6001;  
     //链式前向星  
     public static int[]head=new int[MAXN];  
     public static int[]next=new int[MAXM];  
     public static int[]to=new int[MAXM];  
     public static int[]weight=new int[MAXM];  
     public static int cnt;  
     //反向索引堆  
     public static int[]heap=new int[MAXN];  
     // where[v] = -1,表示v这个节点,从来没有进入过堆  
     // where[v] = -2,表示v这个节点,已经弹出过了  
     // where[v] = i(>=0),表示v这个节点,在堆上的i位置  
     public static int[]where=new int[MAXN];  
     public static int heapSize;  
     public static int[]distance=new int[MAXN];  
   
     public static int networkDelayTime(int[][] times, int n, int k) {  
         build(n);  
         //链式前向星建图  
         for(int[]edge:times){  
             addEdge(edge[0],edge[1],edge[2]);  
         }  
         //开始 Dijkstra算法  
         //源点进入  
         addOrUpdateOrIgnore(k,0);  
         while(!isEmpty()){  
             int u=pop();  
             for(int ei=head[u];ei>0;ei=next[ei]){  
                 addOrUpdateOrIgnore(to[ei],distance[u]+weight[ei]);  
             }  
         }  
         int ans=0;  
         for(int i=1;i<=n;i++){  
             ans=Math.max(ans,distance[i]);  
         }  
         return ans==Integer.MAX_VALUE?-1:ans;  
     }  
   
     public static void build(int n){  
         cnt=1;  
         heapSize=0;  
         Arrays.fill(head,0,n+1,0);  
         Arrays.fill(where,0,n+1,-1);  
         Arrays.fill(distance,0,n+1,Integer.MAX_VALUE);  
     }  
     public static void addEdge(int u,int v,int w){  
         next[cnt]=head[u];  
         to[cnt]=v;  
         weight[cnt]=w;  
         head[u]=cnt++;  
     }  
     //反向堆索引优化  
     /**  
      3,反向索引堆弹出 (u,源点到 u 的距离),考察 u 的每一条边,假设某边去往 v,边权为 w  
      1)如果 v 没有进入过反向索引堆里,新增记录 (v, 源点到 u 的距离 + w)  
      2)如果 v 曾经从反向索引堆弹出过,忽略  
      3)如果 v 在反向索引堆里,看看源点到 v 的距离能不能变得更小,如果能,调整堆;不能,忽略  
      4)处理完 u 的每一条边,重复步骤 3  
      4 反向索引堆为空过程结束。反向索引堆里记录了源点到每个节点的最短距离。  
   
      */  
     public static void addOrUpdateOrIgnore(int v,int c){  
         if(where[v]==-1){  
             //将点加入堆中  
             heap[heapSize]=v;  
             where[v]=heapSize++;  
             distance[v]=c;  
             //新加入的点要网上看,使堆为小根堆  
             heapInsert(where[v]);  
         }else if(where[v]>=0){  
             distance[v]=Math.min(distance[v],c);  
             heapInsert(where[v]);  
         }  
     }  
   
     public static void heapInsert(int i){  
         while(distance[heap[i]]<distance[heap[(i-1)/2]]){  
             swap(i,(i-1)/2);  
             i=(i-1)/2;  
         }  
     }  
   
     public static boolean isEmpty(){  
         return heapSize==0;  
     }  
     public static int pop(){  
         int ans=heap[0];  
         //要复原小根堆  
         swap(0,--heapSize);  
         heapify(0);  
         //弹出后的点标记,后续有关这个点的所有操作都不再处理  
         where[ans]=-2;  
         return ans;  
     }  
   
     public static void heapify(int i){  
         int l=i*2+1;  
         while(l<heapSize){  
             int best=l+1<heapSize && distance[heap[l+1]]<distance[heap[l]]?l+1:l;  
             best=distance[heap[best]]<distance[heap[i]]?best:i;  
             if(best==i){  
                 break;  
             }  
             swap(i,best);  
             i=best;  
             l=2*i+1;  
         }  
     }  
   
     public static void swap(int i,int j){  
         //首先是堆中交换  
         int tmp=heap[i];  
         heap[i]=heap[j];  
         heap[j]=tmp;  
         //不要忘记索引数组的交换  
         where[heap[i]]=i;  
         where[heap[j]]=j;  
     }  
 }

2、最小体力消耗路径

题目描述: 你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往  四个方向之一移动,你想要找到耗费 体力 最小的一条路径。

一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。

请你返回从左上角走到右下角的最小 体力消耗值 。

示例 1:

输入:heights = [[1,2,2],[3,8,2],[5,3,5]] 输出:2 解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。 这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。

示例 2:

输入:heights = [[1,2,3],[3,8,4],[5,3,5]] 输出:1 解释:路径 [1,2,3,4,5] 的相邻格子差值绝对值最大为 1 ,比路径 [1,3,5,3,5] 更优。

示例 3:

输入:heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]] 输出:0 解释:上图所示路径不需要消耗任何体力。

提示:

  • rows == heights.length

  • columns == heights[i].length

  • 1 <= rows, columns <= 100

  • 1 <= heights[i][j] <= 106

项目名称

内容填写(每道题一行填写)

📘 题目编号 / 标题

LeetCode 1631 + 最小体力消耗路径

🧠 题目关键词(原文关键词)

地图数组,找到耗费 体力 最小的一条路径,一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。

🧩 抽象问题类型(题目本质)

找最短路径的模型,只是其中路径的代价被重新定义为了相邻格子之间 高度差绝对值 的 最大值

🔍 数据规模 / 限制

- rows == heights.length - columns == heights[i].length - 1 <= rows, columns <= 100 - 1 <= heights[i][j] <= 106

🧭 我的初步思路

Dijkstra 算法

✅ 正确解法类型

Dijkstra 算法

❗ 没想到的原因

我转化了模型,想到了用 DijKstra 算法,但是在后面自己闹钟演示那个简单的示例的时候,演示的过程忽略了小顶堆每次只弹头结点,误以为一条错误路径只要有终点入了小根堆,就出结果了,但是忽略了它哪怕进去了也不一定立马能出来。而来看到上下左右四个方向,老是想着要用递归,却忽略了 visited 数组,以至于一直不知道怎么写

📦 归入的题型分类

Dijkstra 算法

🧠 触发词(以后遇到就联想)

图中求最短路径,权重不同-->Dijkstra 算法

🧪 解法一句话总结

Dijkstra 算法求解最短路径,注意其中路径代价已经发生变化

 /**  
  * @program: ZuoChengxunAlgorithmClass  
  * @ClassName Code02_PathWithMinimumEffort  
  * @description: 最小体力消耗路径  
  * // 你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights  
  * // 其中 heights[row][col] 表示格子 (row, col) 的高度  
  * // 一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1)  
  * // (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动  
  * // 你想要找到耗费 体力 最小的一条路径  
  * // 一条路径耗费的体力值是路径上,相邻格子之间高度差绝对值的最大值  
  * // 请你返回从左上角走到右下角的最小 体力消耗值  
  * // 测试链接 :https://leetcode.cn/problems/path-with-minimum-effort/  
  * @author: zs宝  
  * @create: 2025-07-23 11:03  
  * @Version 1.0  
  **/  
 package main.java.class064;  
   
 import java.util.PriorityQueue;  
   
 public class Code02_PathWithMinimumEffort {  
     class Solution {  
         public int minimumEffortPath(int[][] heights) {  
             int[]move=new int[]{-1,0,1,0,-1};  
             int n=heights.length;  
             int m=heights[0].length;  
             boolean[][]visited=new boolean[n][m];  
             int[][]distance=new int[n][m];  
             for(int i=0;i<n;i++){  
                 for(int j=0;j<m;j++){  
                     distance[i][j]=Integer.MAX_VALUE;  
                 }  
             }  
             distance[0][0]=0;  
             //0:x  
             //1:y  
             //2: distance            
             PriorityQueue<int[]> heap=new PriorityQueue<>((a, b)->a[2]-b[2]);  
             heap.add(new int[]{0,0,distance[0][0]});  
             while(!heap.isEmpty()){  
                 int[]record=heap.poll();  
                 int x=record[0];  
                 int y=record[1];  
                 int w=record[2];  
                 if(visited[x][y]){  
                     continue;  
                 }  
                 if(x==n-1 && y==m-1){  
                     return w;  
                 }  
                 visited[x][y]=true;  
                 //开始向四周扩散  
                 for(int i=0,nx,ny,nw;i<4;i++){  
                     nx=x+move[i];  
                     ny=y+move[i+1];  
                     if(nx>=0 && nx<n && ny>=0 && ny<m && !visited[nx][ny]){  
                         nw=Math.max(w,Math.abs(heights[x][y]-heights[nx][ny]));  
                         if(nw<distance[nx][ny]){  
                             distance[nx][ny]=nw;  
                             heap.add(new int[]{nx,ny,nw});  
                         }  
   
                     }  
                 }  
             }  
             return -1;  
         }  
     }  
 }

3、水位上升的泳池中游泳

题目描述: 在一个 n x n 的整数矩阵 grid 中,每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度。

当开始下雨时,在时间为 t 时,水池中的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。

你从坐标方格的左上平台 (0,0) 出发。返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。

示例 1:

输入: grid = [[0,2],[1,3]] 输出: 3 解释: 时间为0时,你位于坐标方格的位置为 (0, 0)。 此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。 等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置

示例 2:

输入: grid = [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]] 输出: 16 解释: 最终的路线用加粗进行了标记。 我们必须等到时间为 16,此时才能保证平台 (0, 0) 和 (4, 4) 是连通的

提示:

  • n == grid.length

  • n == grid[i].length

  • 1 <= n <= 50

  • 0 <= grid[i][j] < n2

  • grid[i][j] 中每个值 均无重复

项目名称

内容填写(每道题一行填写)

📘 题目编号 / 标题

LeetCode 778 + 水位上升的泳池中游泳

🧠 题目关键词(原文关键词)

整数矩形,你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。

🧩 抽象问题类型(题目本质)

图中找时间最短的路径

🔍 数据规模 / 限制

- n == grid.length - n == grid[i].length - 1 <= n <= 50 - 0 <= grid[i][j] < n2 - grid[i][j] 中每个值 均无重复

🧭 我的初步思路

Dijkstra 算法

✅ 正确解法类型

Dijkstra 算法

❗ 没想到的原因

对于初始的 distance 数组这对于初始位置应该为 grid0, 而非直接设为 0

📦 归入的题型分类

Dijkstra

🧠 触发词(以后遇到就联想)

最短路径且路径代价不同->Dijkstra 算法

🧪 解法一句话总结

Dijkstra 找出代价中最小的路径

 /**  
  * @program: ZuoChengxunAlgorithmClass  
  * @ClassName Code03_SwimInRisingWater  
  * @description: 水位上升的泳池中游泳  
  * // 在一个 n x n 的整数矩阵 grid 中  
  * // 每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度  
  * // 当开始下雨时,在时间为 t 时,水池中的水位为 t  
  * // 你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台  
  * // 假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的  
  * // 当然,在你游泳的时候你必须待在坐标方格里面。  
  * // 你从坐标方格的左上平台 (0,0) 出发  
  * // 返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间  
  * // 测试链接 : https://leetcode.cn/problems/swim-in-rising-water/  
  * @author: zs宝  
  * @create: 2025-07-24 09:29  
  * @Version 1.0  
  **/  
 package main.java.class064;  
   
 import java.util.PriorityQueue;  
   
 public class Code03_SwimInRisingWater {  
     class Solution {  
         public int swimInWater(int[][] grid) {  
             int[]move=new int[]{-1,0,1,0,-1};  
             int n=grid.length;  
             int[][]distance=new int[n][n];  
             for(int i=0;i<n;i++){  
                 for(int j=0;j<n;j++){  
                     distance[i][j]=Integer.MAX_VALUE;  
                 }  
             }  
             boolean[][]visited=new boolean[n][n];  
             PriorityQueue<int[]> heap=new PriorityQueue<>((a, b)->a[2]-b[2]);  
             //开始Dijkstra算法  
             distance[0][0]=grid[0][0];  
             heap.add(new int[]{0,0,distance[0][0]});  
             while(!heap.isEmpty()){  
                 int[]record=heap.poll();  
                 int x=record[0];  
                 int y=record[1];  
                 int w=record[2];  
                 if(visited[x][y]){  
                     continue;  
                 }  
                 if(x==n-1 && y==n-1){  
                     return w;  
                 }  
                 visited[x][y]=true;  
                 for(int i=0,nx,ny,nw;i<4;i++){  
                     nx=x+move[i];  
                     ny=y+move[i+1];  
                     if(nx>=0 && nx<n && ny>=0 && ny<n && !visited[nx][ny]){  
                         nw=Math.max(w,grid[nx][ny]);  
                         if(nw<distance[nx][ny]){  
                             distance[nx][ny]=nw;  
                             heap.add(new int[]{nx,ny,distance[nx][ny]});  
                         }  
                     }  
                 }  
             }  
             return -1;  
         }  
     }  
 }

4、获取所有钥匙的最短路径

题目描述: 给定一个二维网格 grid ,其中:

  • '.' 代表一个空房间

  • '#' 代表一堵墙

  • '@' 是起点

  • 小写字母代表钥匙

  • 大写字母代表锁

我们从起点开始出发,一次移动是指向四个基本方向之一行走一个单位空间。我们不能在网格外面行走,也无法穿过一堵墙。如果途经一个钥匙,我们就把它捡起来。除非我们手里有对应的钥匙,否则无法通过锁。

假设 k 为 钥匙/锁 的个数,且满足 1 <= k <= 6,字母表中的前 k 个字母在网格中都有自己对应的一个小写和一个大写字母。换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁。另外,代表钥匙和锁的字母互为大小写并按字母顺序排列。

返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 -1 。

示例 1:

输入:grid = ["@.a..","###.#","b.A.B"] 输出:8 解释:目标是获得所有钥匙,而不是打开所有锁。

示例 2:

输入:grid = ["@..aA","..B#.","....b"] 输出:6

示例 3:

输入: grid = ["@Aa"] 输出: -1

提示:

  • m == grid.length

  • n == grid[i].length

  • 1 <= m, n <= 30

  • grid[i][j] 只含有 '.''#''@''a'-``'f``' 以及 'A'-'F'

  • 钥匙的数目范围是 [1, 6] 

  • 每个钥匙都对应一个 不同 的字母

  • 每个钥匙正好打开一个对应的锁

项目名称

内容填写(每道题一行填写)

📘 题目编号 / 标题

LeetCode 864 + 获取所有钥匙的最短路径

🧠 题目关键词(原文关键词)

二维网格 grid ,其中: - '.' 代表一个空房间 - '#' 代表一堵墙 - '@' 是起点 - 小写字母代表钥匙 - 大写字母代表锁,返回获取所有钥匙所需要的移动的最少次数

🧩 抽象问题类型(题目本质)

求解最短路径

🔍 数据规模 / 限制

- m == grid.length - n == grid[i].length - 1 <= m, n <= 30 - grid[i][j] 只含有 '.''#''@''a'-``'f``' 以及 'A'-'F' - 钥匙的数目范围是 [1, 6]  - 每个钥匙都对应一个 不同 的字母 - 每个钥匙正好打开一个对应的锁

🧭 我的初步思路

Bfs

✅ 正确解法类型

分层最短路径+bfs

❗ 没想到的原因

这里的每个点不再只是 (x, y) 本身,而是(x, y, 状态)

📦 归入的题型分类

分层最短路径

🧠 触发词(以后遇到就联想)

有关每个点加状态的最短路径问题->分层图最短路径法

🧪 解法一句话总结

将每个坐标+当前状态作为一个点,在利用 bfs 近最短路径寻找

 /**  
  * @program: ZuoChengxunAlgorithmClass  
  * @ClassName Code04_ShortestPathToGetAllKeys  
  * @description:获取所有钥匙的最短路径  
  * // 给定一个二维网格grid,其中:  
  * // '.' 代表一个空房间、'#' 代表一堵、'@'是起点  
  * // 小写字母代表钥匙、大写字母代表锁  
  * // 从起点开始出发,一次移动是指向四个基本方向之一行走一个单位空间  
  * // 不能在网格外面行走,也无法穿过一堵墙  
  * // 如果途经一个钥匙,我们就把它捡起来。除非我们手里有对应的钥匙,否则无法通过锁。  
  * // 假设 k为 钥匙/锁 的个数,且满足1 <= k<= 6,  
  * // 字母表中的前 k个字母在网格中都有自己对应的一个小写和一个大写字母  
  * // 换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁  
  * // 另外,代表钥匙和锁的字母互为大小写并按字母顺序排列  
  * // 返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回-1。  
  * // 测试链接:https://leetcode.cn/problems/shortest-path-to-get-all-keys  
  * @author: zs宝  
  * @create: 2025-07-24 10:02  
  * @Version 1.0  
  **/  
 package main.java.class064;  
   
 public class Code04_ShortestPathToGetAllKeys {  
     class Solution {  
         public static int MAXM=31;  
         public static int MAXN=31;  
         public static int MAXK=6;  
         //图的网格  
         public static char[][] grid=new char[MAXM][];  
         //是否经过某个点的某个状态  
         public static boolean[][][]visited=new boolean[MAXM][MAXN][1<<MAXK];  
         //队列  
         //0:x  
         //1: y        //2: 当前的状态(身上的有哪些钥匙)  
         public static int[][]queue=new int[MAXM*MAXN*(1<<MAXK)][3];  
         public static int l,r,n,m,key;  
         public static int[]move=new int[]{-1,0,1,0,-1};  
         public int shortestPathAllKeys(String[] g) {  
             build(g);  
             int level=1;  
             //bfs  
             while(l<r){  
                 int size=r-l;  
                 for(int k=0,x,y,s;k<size;k++){  
                     //当前状态  
                     x=queue[l][0];  
                     y=queue[l][1];  
                     s=queue[l++][2];  
                     //向四周寻找  
                     for(int i=0,nx,ny,ns;i<4;i++){  
                         nx=x+move[i];  
                         ny=y+move[i+1];  
                         ns=s;  
                         //如果越界或者撞到了墙上,则此路不通  
                         if(nx<0 || nx>=m || ny<0 || ny>=n || grid[nx][ny]=='#'){  
                             continue;  
                         }  
                         //如果遇到了锁,但是身上并没有其钥匙,那么此路不通  
                         if(grid[nx][ny]>='A' && grid[nx][ny]<='F' && (ns&(1<<(grid[nx][ny]-'A')))==0){  
                             continue;  
                         }  
                         //如果遇到一把钥匙,则收集  
                         if(grid[nx][ny]>='a' && grid[nx][ny]<='f'){  
                             ns|=(1<<(grid[nx][ny]-'a'));  
                         }  
                         //如果钥匙收集全了,则直接返回结果,一个剪枝步骤  
                         if(ns==key){  
                             return level;  
                         }  
                         //如果当前点状态未被参观,则加入参观,同时将其近期如队列  
                         if(!visited[nx][ny][ns]){  
                             visited[nx][ny][ns]=true;  
                             queue[r][0]=nx;  
                             queue[r][1]=ny;  
                             queue[r++][2]=ns;  
                         }  
                     }  
                 }  
                 level++;  
             }  
             return -1;  
         }  
         public static void build(String[]g){  
             //首先将字符数组转换为char的网格二维数组  
             m=g.length;  
             n=g[0].length();  
             for(int i=0;i<m;i++){  
                 grid[i]=g[i].toCharArray();  
             }  
             l=r=key=0;  
             //找到初始位置出发点、钥匙收集全的状态  
             for(int i=0;i<m;i++){  
                 for(int j=0;j<n;j++){  
                     //找到初始位置  
                     if(grid[i][j]=='@'){  
                         queue[r][0]=i;  
                         queue[r][1]=j;  
                         queue[r++][2]=0;  
                     }  
                     //收集钥匙的状态  
                     if(grid[i][j]>='a' && grid[i][j]<='f'){  
                         key|=1<<(grid[i][j]-'a');  
                     }  
                 }  
             }  
             //visited数组的遍历状态  
             for(int i=0;i<m;i++){  
                 for(int j=0;j<n;j++){  
                     for(int s=0;s<=key;s++){  
                         visited[i][j][s]=false;  
                     }  
                 }  
             }  
         }  
     }  
 }

5、电动车游城市

题目描述: 小明的电动车电量充满时可行驶距离为 cnt,每行驶 1 单位距离消耗 1 单位电量,且花费 1 单位时间。小明想选择电动车作为代步工具。地图上共有 N 个景点,景点编号为 0 ~ N-1。他将地图信息以 [城市 A 编号,城市 B 编号,两城市间距离] 格式整理在在二维数组 paths,表示城市 A、B 间存在双向通路。初始状态,电动车电量为 0。每个城市都设有充电桩,charge[i] 表示第 i 个城市每充 1 单位电量需要花费的单位时间。请返回小明最少需要花费多少单位时间从起点城市 start 抵达终点城市 end

示例 1:

输入:paths = [[1,3,3],[3,2,1],[2,1,3],[0,1,4],[3,0,5]], cnt = 6, start = 1, end = 0, charge = [2,10,4,1] 输出:43 解释:最佳路线为:1->3->0。 在城市 1 仅充 3 单位电至城市 3,然后在城市 3 充 5 单位电,行驶至城市 0。 充电用时共 310 + 51= 35 行驶用时 3 + 5 = 8,此时总用时最短 43。

image.png

示例 2:

输入:paths = [[0,4,2],[4,3,5],[3,0,5],[0,1,5],[3,2,4],[1,2,8]], cnt = 8, start = 0, end = 2, charge = [4,1,1,3,2] 输出:38 解释:最佳路线为:0->4->3->2。 城市 0 充电 2 单位,行驶至城市 4 充电 8 单位,行驶至城市 3 充电 1 单位,最终行驶至城市 2。 充电用时 42+28+3*1 = 27 行驶用时 2+5+4 = 11,总用时最短 38。

提示:

  • 1 <= paths.length <= 200

  • paths[i].length == 3

  • 2 <= charge.length == n <= 100

  • 0 <= path[i][0],path[i][1],start,end < n

  • 1 <= cnt <= 100

  • 1 <= path[i][2] <= cnt

  • 1 <= charge[i] <= 100

  • 题目保证所有城市相互可以到达

项目名称

内容填写(每道题一行填写)

📘 题目编号 / 标题

LeetCode 35 + 电动车游城市

🧠 题目关键词(原文关键词)

城市 A、B 间存在双向通路,最少需要花费多少单位时间从起点城市 start 抵达终点城市 end

🧩 抽象问题类型(题目本质)

求代价最短路径

🔍 数据规模 / 限制

- 1 <= paths.length <= 200 - paths[i].length == 3 - 2 <= charge.length == n <= 100 - 0 <= path[i][0],path[i][1],start,end < n - 1 <= cnt <= 100 - 1 <= path[i][2] <= cnt - 1 <= charge[i] <= 100 - 题目保证所有城市相互可以到达

🧭 我的初步思路

每次在当前城市点找到邻接城市,每次充电可以到达到一个城市的电量,利用 Dijkstra 算法进行最短路径的寻找

✅ 正确解法类型

分层组最短路径

❗ 没想到的原因

每次在一个城市技巧本身的状态为一个点,充电充多少,或者不充电直接走对于每一个状态都需要考量,最初的思路正是少了对每种状态的考量。根本原因在于本身不知道如何划分状态,如何如何扩点、如何到达、如何算距离,对于题目这点究竟是城市本身还是城市加状态区分不开。

📦 归入的题型分类

分层组最短路径

🧠 触发词(以后遇到就联想)

有关每个点是要带状态的最短路径问题(即哪怕在同一个图的电上,但是只要转台不同,那么这就是一个不同的点)->分层图最短路径法

🧪 解法一句话总结

将每个坐标+当前状态作为一个点,再利用 Dijkstra 进行最短路径寻找

 /**  
  * @program: ZuoChengxunAlgorithmClass  
  * @ClassName Code05_VisitCityMinCost  
  * @description: 电动车游城市  
  * // 小明的电动车电量充满时可行驶距离为 cnt,每行驶 1 单位距离消耗 1 单位电量,且花费 1 单位时间  
  * // 小明想选择电动车作为代步工具。地图上共有 N 个景点,景点编号为 0 ~ N-1  
  * // 他将地图信息以 [城市 A 编号,城市 B 编号,两城市间距离] 格式整理在在二维数组 paths,  
  * // 表示城市 A、B 间存在双向通路。  
  * // 初始状态,电动车电量为 0。每个城市都设有充电桩,  
  * // charge[i] 表示第 i 个城市每充 1 单位电量需要花费的单位时间。  
  * // 请返回小明最少需要花费多少单位时间从起点城市 start 抵达终点城市 end  
  * // 测试链接 : https://leetcode.cn/problems/DFPeFJ/  
  * @author: zs宝  
  * @create: 2025-07-24 11:24  
  * @Version 1.0  
  **/  
 package main.java.class064;  
   
 import java.util.ArrayList;  
 import java.util.PriorityQueue;  
   
 public class Code05_VisitCityMinCost {  
     class Solution {  
         public int electricCarPlan(int[][] paths, int cnt, int start, int end, int[] charge) {  
             int n=charge.length;  
             //建图  
             ArrayList<ArrayList<int[]>>graph=new ArrayList<>();  
             for(int i=0;i<n;i++){  
                 graph.add(new ArrayList<>());  
             }  
             for(int[] path:paths){  
                 //注意是无向图  
                 graph.get(path[0]).add(new int[]{path[1],path[2]});  
                 graph.get(path[1]).add(new int[]{path[0],path[2]});  
             }  
             //距离数组  
             //这里的距离是代价,由于每个城市对应的电动车的电量可以有cnt种  
             //因此这里每个点指的不再是城市或者道路节点,而是到达某个城市时自身拥有的一个状态为一个点(电动车电量)  
             //由于电量是1-cnt因此数组要有cnt+1个才能对应装  
             int[][]distance=new int[n][cnt+1];  
             for(int i=0;i<n;i++){  
                 for(int j=0;j<=cnt;j++){  
                     distance[i][j]=Integer.MAX_VALUE;  
                 }  
             }  
             //构建参观数组  
             boolean [][]visited=new boolean[n][cnt+1];  
             //由于题目本质还是找花费最小代价的最短路径问题,且题目边的权重不同,因此用Dijkstra算法  
             //0:城市点;1:电量;2:代价  
             PriorityQueue<int[]> heap=new PriorityQueue<>((a, b)->a[2]-b[2]);  
             //由于初始状态电动车无电  
             distance[start][0]=0;  
             heap.add(new int[]{start,0,distance[start][0]});  
             while(!heap.isEmpty()){  
                 int[]record=heap.poll();  
                 //当前城市  
                 int cur=record[0];  
                 //当前电量  
                 int power=record[1];  
                 //代价  
                 int cost=record[2];  
                 if(visited[cur][power]){  
                     continue;  
                 }  
                 if(cur==end){  
                     return cost;  
                 }  
                 visited[cur][power]=true;  
                 //接下来就是这个题目的关键,这也是分层最短路径与普通的寻找最短路径最大的区别,本身带有太多状态的考量  
                 //当前点充电否,是不充电直接开往下一个城市,还是充电开往下一个城市,充电充多少  
   
                 //如果充电,我们每次充一格点加入heap小根堆,后续每次弹出都会再充一格点直到状态遍历完  
                 if(power+1<=cnt && !visited[cur][power+1]){  
                     if(cost+charge[cur]<distance[cur][power+1]){  
                         distance[cur][power+1]=cost+charge[cur];  
                         heap.add(new int[]{cur,power+1,distance[cur][power+1]});  
                     }  
                 }  
                 //不充电直接开往下一个城市  
                 for(int[]path:graph.get(cur)){  
                     int nextCity=path[0];  
                     int nextPower=power-path[1];  
                     int nextCost=cost+path[1];  
                     if(nextPower>=0 && !visited[nextCity][nextPower]&& nextCost < distance[nextCity][nextPower]){  
                         heap.add(new int[]{nextCity,nextPower,nextCost});  
                     }  
                 }  
             }  
             return -1;  
         }  
     }  
 }

6、飞行路线

题目描述

Alice 和 Bob 现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在 n 个城市设有业务,设这些城市分别标记为 0 到 n-1,一共有 m 种航线,每种航线连接两个城市,并且航线有一定的价格。

Alice 和 Bob 现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多 k 种航线上搭乘飞机。那么 Alice 和 Bob 这次出行最少花费多少? 输入格式

第一行三个整数 n,m,k,分别表示城市数,航线数和免费乘坐次数。

接下来一行两个整数 s,t,分别表示他们出行的起点城市编号和终点城市编号。

接下来 m 行,每行三个整数 a,b,c,表示存在一种航线,能从城市 a 到达城市 b,或从城市 b 到达城市 a,价格为 c。

输出格式

输出一行一个整数,为最少花费。

输入输出样例

输入

5 6 1
0 4
0 1 5
1 2 5
2 3 5
3 4 5
2 3 3
0 2 100

输出

8

说明/提示

数据规模与约定

对于 30\% 的数据,2 \le n \le 50,1 \le m \le 300,k=0。

对于 50\% 的数据,2 \le n \le 600,1 \le m \le 6\times10^3,0 \le k \le 1。

对于 100\% 的数据,2 \le n \le 10^4,1 \le m \le 5\times 10^4,0 \le k \le 10,0\le s,t,a,b < n,a\ne b,0\le c\le 10^3。

另外存在一组 hack 数据。

项目名称

内容填写(每道题一行填写)

📘 题目编号 / 标题

LG P 4568 + 飞行路线

🧠 题目关键词(原文关键词)

最少花费多少

🧩 抽象问题类型(题目本质)

分层组求最短路径

🔍 数据规模 / 限制

对于 30% 的数据,2≤n≤50,1≤m≤300,k=0。 对于 50% 的数据,2≤n≤600,1≤m≤6×103,0≤k≤1。 对于 100% 的数据,2≤n≤104,1≤m≤5×104,0≤k≤10,0≤s,t,a,b<n,a\=b,0≤c≤103。 另外存在一组 hack 数据。

🧭 我的初步思路

分层组最短路径

✅ 正确解法类型

分层组最短路径

❗ 没想到的原因

想到了。但中间实现处理点问题,不知道分别讨论免费和不免费时需不需要放入下一个 path 中

📦 归入的题型分类

分层组最短路径

🧠 触发词(以后遇到就联想)

有关每个点是要带状态的最短路径问题(即哪怕在同一个图的点上,但是只要转台不同,那么这就是一个不同的点)->分层图最短路径法

🧪 解法一句话总结

将每个坐标+当前状态作为一个点,再利用 Dijkstra 进行最短路径寻找

 /**  
  * @program: ZuoChengxunAlgorithmClass  
  * @ClassName Code06_FlightPath1  
  * @description:飞行路线(语言提供的堆)  
  * // Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司  
  * // 该航空公司一共在n个城市设有业务,设这些城市分别标记为0 ~ n−1  
  * // 一共有m种航线,每种航线连接两个城市,并且航线有一定的价格  
  * // Alice 和 Bob 现在要从一个城市沿着航线到达另一个城市,途中可以进行转机  
  * // 航空公司对他们这次旅行也推出优惠,他们可以免费在最多k种航线上搭乘飞机  
  * // 那么 Alice 和 Bob 这次出行最少花费多少  
  * // 测试链接 : https://www.luogu.com.cn/problem/P4568  
  * @author: zs宝  
  * @create: 2025-07-25 10:45  
  * @Version 1.0  
  **/  
 package main.java.class064;  
   
 import java.io.*;  
 import java.util.Arrays;  
   
 public class Code06_FlightPath {  
     public static int MAXN=10001;  
     //注意无向图,最大的边数*2  
     public static int MAXM=100001;  
     public static int MAXK=11;  
     public static int n,m,k,start,end;  
     //建图的链式前向星  
     public static int[]head=new int[MAXN];  
     public static int[]next=new int[MAXM];  
     public static int[]to=new int[MAXM];  
     public static int[]weight=new int[MAXM];  
     public static int cnt;  
   
     //代价数组  
     //在某个城市乘坐了第几次免费航线,当前的代价是多少  
     //这里的点是城市+乘坐免费航线次数的组合  
     public static int[][]distance=new int[MAXN][MAXK];  
     public static boolean[][] visited = new boolean[MAXN][MAXK];  
     //Dijkstra算法的小根堆  
     //自己写的堆优化  
     public static int[][]heap=new int[MAXN*MAXK][3];  
     public static int heapSize;  
   
     public static void main(String[] args) throws IOException {  
         BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));  
         StreamTokenizer in=new StreamTokenizer(bufferedReader);  
         PrintWriter out=new PrintWriter(new OutputStreamWriter(System.out));  
         while (in.nextToken()!=StreamTokenizer.TT_EOF){  
             n=(int)in.nval;  
             in.nextToken();  
             m=(int)in.nval;  
             in.nextToken();  
             k=(int)in.nval;  
             in.nextToken();  
             start=(int)in.nval;  
             in.nextToken();  
             end=(int)in.nval;  
             build();  
             for(int i=0,a,b,c;i<m;i++){  
                 in.nextToken();  
                 a=(int)in.nval;  
                 in.nextToken();  
                 b=(int)in.nval;  
                 in.nextToken();  
                 c=(int)in.nval;  
                 addEdge(a,b,c);  
                 addEdge(b,a,c);  
             }  
              out.println(dijkstra());  
         }  
         out.flush();  
         out.close();  
         bufferedReader.close();  
     }  
     public static void build(){  
         //链式前向星初始化  
         cnt=1;  
         Arrays.fill(head,0,n+1,0);  
         //距离数组初始化  
         for(int i=0;i<n;i++){  
             for(int j=0;j<=k;j++){  
                 distance[i][j]=Integer.MAX_VALUE;  
                 visited[i][j]=false;  
             }  
         }  
         //堆初始化  
         heapSize=0;  
   
     }  
     //链式前向星加边  
     public static void addEdge(int u,int v,int w){  
         next[cnt]=head[u];  
         to[cnt]=v;  
         weight[cnt]=w;  
         head[u]=cnt++;  
     }  
     //用来代表小根堆每次弹出的节点中的数据  
     public static int cur,use,cost;  
     public static int dijkstra(){  
         distance[start][0]=0;  
         //小根堆加入  
         push(start,0,0);  
         while(!isEmpty()){  
             //弹出小根堆根节点  
             pop();  
             if(visited[cur][use]){  
                 continue;  
             }  
             if(cur==end){  
                 return cost;  
             }  
             visited[cur][use]=true;  
             //对于下一段航线,分别尝试使用免费和不适用免费  
             for(int ei=head[cur];ei>0;ei=next[ei]){  
                 int next = to[ei];  
                 int w=weight[ei];  
                 //使用免费  
                 if(use<k && !visited[next][use+1] && distance[cur][use]<distance[next][use+1]){  
                     distance[next][use+1]=distance[cur][use];  
                     push(next,use+1,distance[next][use+1]);  
                 }  
                 //不使用免费  
                 if(!visited[next][use] && distance[cur][use]+w<distance[next][use]){  
                     distance[next][use]=distance[cur][use]+w;  
                     push(next,use,distance[next][use]);  
                 }  
             }  
         }  
         return -1;  
     }  
   
     public static void push(int u,int t,int c){  
         heap[heapSize][0]=u;  
         heap[heapSize][1]=t;  
         heap[heapSize][2]=c;  
         heapInsert(heapSize++);  
     }  
     public static void heapInsert(int i){  
         while (i>0 && heap[i][2]<heap[(i-1)/2][2]){  
             swap(i,(i-1)/2);  
             i=(i-1)/2;  
         }  
     }  
     public static void swap(int i,int j){  
         int[]temp=heap[i];  
         heap[i]=heap[j];  
         heap[j]=temp;  
     }  
   
     public static void pop(){  
         cur=heap[0][0];  
         use=heap[0][1];  
         cost=heap[0][2];  
         swap(0,--heapSize);  
         heapfy(0);  
     }  
     public static void heapfy(int i){  
         int l=2*i+1;  
         while (l<heapSize){  
             int best=(l+1)<heapSize && heap[l+1][2]<heap[l][2]?l+1:l;  
             best=heap[best][2]<heap[i][2]?best:i;  
             if(best==i){  
                 break;  
             }  
             swap(best,i);  
             i=best;  
             l=2*i+1;  
         }  
     }  
   
     public static boolean isEmpty(){  
         return heapSize==0;  
     }  
   
 }

参考资料