从第一个节点出发到最后一个节点的受限路径数
难度:
标签:
题目描述
现有一个加权无向连通图。给你一个正整数 n
,表示图中有 n
个节点,并按从 1
到 n
给节点编号;另给你一个数组 edges
,其中每个 edges[i] = [ui, vi, weighti]
表示存在一条位于节点 ui
和 vi
之间的边,这条边的权重为 weighti
。
从节点 start
出发到节点 end
的路径是一个形如 [z0, z1, z2, ..., zk]
的节点序列,满足 z0 = start
、zk = end
且在所有符合 0 <= i <= k-1
的节点 zi
和 zi+1
之间存在一条边。
路径的距离定义为这条路径上所有边的权重总和。用 distanceToLastNode(x)
表示节点 n
和 x
之间路径的最短距离。受限路径 为满足 distanceToLastNode(zi) > distanceToLastNode(zi+1)
的一条路径,其中 0 <= i <= k-1
。
返回从节点 1
出发到节点 n
的 受限路径数 。由于数字可能很大,请返回对 109 + 7
取余 的结果。
示例 1:

输入:n = 5, edges = [[1,2,3],[1,3,3],[2,3,1],[1,4,2],[5,2,2],[3,5,1],[5,4,10]] 输出:3 解释:每个圆包含黑色的节点编号和蓝色的 distanceToLastNode 值。三条受限路径分别是: 1) 1 --> 2 --> 5 2) 1 --> 2 --> 3 --> 5 3) 1 --> 3 --> 5
示例 2:

输入:n = 7, edges = [[1,3,1],[4,1,2],[7,3,4],[2,5,3],[5,6,1],[6,7,2],[7,5,3],[2,6,4]] 输出:1 解释:每个圆包含黑色的节点编号和蓝色的 distanceToLastNode 值。唯一一条受限路径是:1 --> 3 --> 7 。
提示:
1 <= n <= 2 * 104
n - 1 <= edges.length <= 4 * 104
edges[i].length == 3
1 <= ui, vi <= n
ui != vi
1 <= weighti <= 105
- 任意两个节点之间至多存在一条边
- 任意两个节点之间至少存在一条路径
代码结果
运行时间: 286 ms, 内存: 39.2 MB
/*
题目思路:
1. 使用Dijkstra算法计算从节点n到所有其他节点的最短距离。
2. 使用Java Stream API简化遍历和过滤操作。
3. 通过倒序DP计算从节点1到节点n的受限路径数。
*/
import java.util.*;
import java.util.stream.*;
public class RestrictedPathsStream {
private static final int MOD = 1000000007;
public int countRestrictedPaths(int n, int[][] edges) {
// 构建图
Map<Integer, List<int[]>> graph = IntStream.rangeClosed(0, n)
.boxed()
.collect(Collectors.toMap(i -> i, i -> new ArrayList<>()));
Arrays.stream(edges).forEach(edge -> {
graph.get(edge[0]).add(new int[]{edge[1], edge[2]});
graph.get(edge[1]).add(new int[]{edge[0], edge[2]});
});
// Dijkstra算法计算从n到所有其他节点的最短路径
int[] dist = new int[n + 1];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[n] = 0;
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[1]));
pq.offer(new int[]{n, 0});
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int u = curr[0], d = curr[1];
if (d > dist[u]) continue;
graph.get(u).stream()
.filter(next -> dist[next[0]] > dist[u] + next[1])
.forEach(next -> {
int v = next[0], w = next[1];
dist[v] = dist[u] + w;
pq.offer(new int[]{v, dist[v]});
});
}
// 使用DFS计算受限路径的数量
Integer[] dp = new Integer[n + 1];
return dfs(1, n, graph, dist, dp);
}
private int dfs(int node, int n, Map<Integer, List<int[]>> graph, int[] dist, Integer[] dp) {
if (node == n) return 1;
if (dp[node] != null) return dp[node];
return dp[node] = graph.get(node).stream()
.filter(next -> dist[node] > dist[next[0]])
.map(next -> dfs(next[0], n, graph, dist, dp))
.reduce(0, (a, b) -> (a + b) % MOD);
}
}
解释
方法:
题解使用了Dijkstra算法来找出从节点n到所有其他节点的最短路径,并且用动态规划(DP)来计算受限路径的数量。首先,使用Dijkstra算法初始化最短路径数组dis,其中dis[n-1]设为0,其他节点设为无穷大,表示从n出发到该节点的最短距离。通过优先队列(堆)来保持当前的最小距离,从n开始向其他节点更新距离。对于每个节点x,检查它的所有邻接节点y,如果通过x到y的距离更短,则更新dis[y]。同时,如果dis[y] > dis[x],说明y到x是受限路径的一部分,将从x到y的受限路径数量累加到f[y]。最后,从节点1到n的受限路径数存储在f[0]中,即最终结果。
时间复杂度:
O((n + edges.length) log n)
空间复杂度:
O(n + edges.length)
代码细节讲解
🦆
为什么在Dijkstra算法中初始化除节点n之外的所有节点距离为无穷大?
▷🦆
在Dijkstra算法实现中,为什么在遇到当前距离大于已知最短距离时选择继续而不是直接停止算法?
▷🦆
题解中提到如果dis[y] > dis[x],则y到x的路径是受限路径的一部分。这种判断条件是否总是正确?存在哪些潜在的异常情况?
▷🦆
动态规划数组f的更新规则是什么?如何确保计算的受限路径数量是准确的?
▷