3123. 最短路径中的边

给你一个 n 个节点的无向带权图,节点编号为 0n - 1 。图中总共有 m 条边,用二维数组 edges 表示,其中 edges[i] = [ai, bi, wi] 表示节点 aibi 之间有一条边权为 wi 的边。

对于节点 0 为出发点,节点 n - 1 为结束点的所有最短路,你需要返回一个长度为 mboolean 数组 answer ,如果 edges[i] 至少 在其中一条最短路上,那么 answer[i]true ,否则 answer[i]false

请你返回数组 answer

注意,图可能不连通。

示例 1:

**输入:**n = 6, edges = [[0,1,4],[0,2,1],[1,3,2],[1,4,3],[1,5,1],[2,3,1],[3,5,3],[4,5,2]]

输出:[true,true,true,false,true,true,true,false]

解释:

以下为节点 0 出发到达节点 5 的 所有 最短路:

  • 路径 0 -> 1 -> 5 :边权和为 4 + 1 = 5
  • 路径 0 -> 2 -> 3 -> 5 :边权和为 1 + 1 + 3 = 5
  • 路径 0 -> 2 -> 3 -> 1 -> 5 :边权和为 1 + 1 + 2 + 1 = 5

示例 2:

**输入:**n = 4, edges = [[2,0,1],[0,1,1],[0,3,4],[3,2,2]]

输出:[true,false,false,true]

解释:

只有一条从节点 0 出发到达节点 3 的最短路 0 -> 2 -> 3 ,边权和为 1 + 2 = 3

提示:

  • 2 <= n <= 5 * 10^4
  • m == edges.length
  • 1 <= m <= min(5 * 10^4, n * (n - 1) / 2)
  • 0 <= ai, bi < n
  • ai != bi
  • 1 <= wi <= 10^5
  • 图中没有重边。

记录父节点进行回溯的Dijkstra

题目需要求解哪些边是在最短路径上的,所以在求最短路的同时,我们需要记录当前最短路节点的父节点,在求得最短路径后(可能不止一条),我们通过每个节点的父节点进行回溯,即可找到最短路上的边

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Solution {
public boolean[] findAnswer(int n, int[][] edges) {
List<int[]> g[] = new ArrayList[n]; // 邻接表建图
Arrays.setAll(g, i -> new ArrayList<>());
HashMap<Integer, Integer> map = new HashMap<>(); // 记录edge在res数组中的位置坐标
for (int i = 0; i < edges.length; i++) {
int edge[] = edges[i];
int x = edge[0], y = edge[1], w = edge[2];
g[x].add(new int[]{y, w});
g[y].add(new int[]{x, w});
map.put(x*n+y, i);
map.put(y*n+x, i);
}
int dis[] = new int[n];
Arrays.fill(dis, Integer.MAX_VALUE);
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // Dijkstra使用优先队列
dis[0] = 0;
pq.offer(new int[]{0, 0});
Set<Integer> p[] = new HashSet[n]; // 记录最短路径中每个节点的父节点们(经过这个节点的最短路径可能不止一条)
Arrays.setAll(p, i -> new HashSet<>());
while (!pq.isEmpty()) {
int poll[] = pq.poll(), cur = poll[0], curDis = poll[1];
for (int nxt[] : g[cur]) {
int y = nxt[0], w = nxt[1];
if (curDis + w <= dis[y]) { // 找到这个节点的最短路径(因为最短路径可能不止一条,这里都需要记录,所以这里是小于等于)
if (curDis + w < dis[y]) { // 如果这个节点有更短的路径,则清空之前记录的父节点
p[y] = new HashSet<>();
}
p[y].add(cur);
dis[y] = curDis + w;
pq.offer(new int[]{y, dis[y]});
}
}
}
// 通过p数组bfs回溯,找到所有最短路径经过的边
Deque<Integer> dq = new ArrayDeque<>();
dq.offer(n-1);
boolean res[] = new boolean[edges.length];
while (!dq.isEmpty()) {
int poll = dq.poll();
for (int pa : p[poll]) {
if (res[map.get(pa*n+poll)] == true) continue;
res[map.get(pa*n+poll)] = true;
dq.offer(pa);
}
}
return res;
}
}

双边Dijkstra

正向dijkstra获得节点0节点n-1的最短路径:dis[]

反向dijkstra获得节点n-1节点0的最短路径:dis2[]

判断每一条边是否在最短路径上,比如一条边:(u,v,w),我们可以通过判断 dis[x] + dis2[y] + w == dis[n-1] || dis[y] + dis2[x] + w == dis[n-1] 来判断这一条边是否在最短路上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
public boolean[] findAnswer(int n, int[][] edges) {
List<int[]> g[] = new ArrayList[n]; // 邻接表建图
Arrays.setAll(g, i -> new ArrayList<>());
for (int i = 0; i < edges.length; i++) {
int edge[] = edges[i];
int x = edge[0], y = edge[1], w = edge[2];
g[x].add(new int[]{y, w});
g[y].add(new int[]{x, w});
}
// 正向dijkstra
int dis[] = new int[n];
Arrays.fill(dis, Integer.MAX_VALUE);
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // Dijkstra使用优先队列
dis[0] = 0;
pq.offer(new int[]{0, 0});
while (!pq.isEmpty()) {
int poll[] = pq.poll(), cur = poll[0], d = poll[1];
for (int nxt[] : g[cur]) {
int y = nxt[0], w = nxt[1];
if (d + w < dis[y]) { // 找到这个节点的最短路径
dis[y] = d + w;
pq.offer(new int[]{y, dis[y]});
}
}
}
// 反向dijkstra
int dis2[] = new int[n];
Arrays.fill(dis2, Integer.MAX_VALUE);
dis2[n-1] = 0;
pq.offer(new int[]{n-1, 0});
while (!pq.isEmpty()) {
int poll[] = pq.poll(), cur = poll[0], d = poll[1];
for (int nxt[] : g[cur]) {
int y = nxt[0], w = nxt[1];
if (d + w < dis2[y]) { // 找到这个节点的最短路径
dis2[y] = d + w;
pq.offer(new int[]{y, dis2[y]});
}
}
}
boolean res[] = new boolean[edges.length];
// 判断每一条边是否在最短路径上(最短路径可能多条)
for (int i = 0; i < edges.length; i++) {
int e[] = edges[i], x = e[0], y = e[1], w = e[2];
if (dis[x] + dis2[y] + w == dis[n-1] || dis[y] + dis2[x] + w == dis[n-1]) res[i] = true;
}
return res;
}
}