leetcode
leetcode 1601 ~ 1650
从第一个节点出发到最后一个节点的受限路径数

从第一个节点出发到最后一个节点的受限路径数

难度:

标签:

题目描述

现有一个加权无向连通图。给你一个正整数 n ,表示图中有 n 个节点,并按从 1n 给节点编号;另给你一个数组 edges ,其中每个 edges[i] = [ui, vi, weighti] 表示存在一条位于节点 uivi 之间的边,这条边的权重为 weighti

从节点 start 出发到节点 end 的路径是一个形如 [z0, z1, z2, ..., zk] 的节点序列,满足 z0 = startzk = end 且在所有符合 0 <= i <= k-1 的节点 zizi+1 之间存在一条边。

路径的距离定义为这条路径上所有边的权重总和。用 distanceToLastNode(x) 表示节点 nx 之间路径的最短距离。受限路径 为满足 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算法中,将除了起始节点(在此例中是节点n)之外的所有节点的距离初始化为无穷大,是为了确保在开始算法时,除起始节点外的其他节点都未被访问过,其最短路径未知。这种初始化方式便于在算法执行过程中,通过比较新计算的距离与已存储的距离来决定是否更新节点的最短路径。只有当通过某条路径发现更短的路径时,该节点的距离才会从无穷大更新为一个具体的值。
🦆
在Dijkstra算法实现中,为什么在遇到当前距离大于已知最短距离时选择继续而不是直接停止算法?
在Dijkstra算法的实现中,当从优先队列中取出一个节点x的距离dx,并发现dx大于该节点当前已知的最短距离dis[x]时,这通常意味着这个节点x已经被处理过,且已经找到了更短的路径到达它。这个较大的dx可能来源于之前的某个状态推入堆中的较远路径。在这种情况下,选择忽略这个较大的距离(即继续处理其他节点),而不是停止算法,是因为堆中可能还存在其他节点的更短路径需要处理。直接停止算法可能导致未处理到其他节点的最短路径。
🦆
题解中提到如果dis[y] > dis[x],则y到x的路径是受限路径的一部分。这种判断条件是否总是正确?存在哪些潜在的异常情况?
题解中提到的判断条件基于从节点n到其他节点的最短路径的递增性来定义受限路径。理论上,如果在最短路径树中,节点y到节点x的路径满足dis[y] > dis[x],则这条路径确实可能是一条受限路径。然而,这个条件是在最短路径已经确定的情况下判断的。如果存在多条相同长度的最短路径,或者Dijkstra算法的执行中路径更新不完全(例如由于算法提前终止或其他原因),这种情况下可能会导致误判。
🦆
动态规划数组f的更新规则是什么?如何确保计算的受限路径数量是准确的?
动态规划数组f用于记录从节点n到每个节点的受限路径数量。数组中f[x]表示从节点n到节点x的受限路径数量。更新规则如下:当发现一条从节点x到节点y的受限路径(即dis[y] > dis[x]),则f[y]的值会加上f[x]的值,因为所有到x的受限路径现在可以扩展到y。为了防止整数溢出,还应对1000000007取模。这种方法确保了在每次找到符合受限路径条件的节点对时,从源节点到目标节点的路径数量正确地累加,从而准确计算最终的受限路径数量。

相关问题