We have a weighted undirected graph and need to find two things: the shortest path distance from node 0 to node n-1, and the number of distinct shortest paths that achieve that distance.
This is an extension of Dijkstra's algorithm. Standard Dijkstra finds the shortest distance, but here we also need to count how many different paths produce that same shortest distance. We maintain a count array alongside the distance array. When we find a shorter path to a node, we reset its count. When we find another path with the same shortest distance, we add to the count.
Whenever Dijkstra relaxes an edge and finds a new shortest distance to some node, the count of shortest paths to that node equals the count of the node we came from. If we find an equally short path through a different node, we add that node's count to the running total.
1 <= n <= 200 → The graph has at most 200 nodes, small enough that even an O(n^3) approach would finish in time.n - 1 <= roads.length <= n * (n - 1) / 2 → The graph is connected (at least n-1 edges) and can be dense (up to ~20,000 edges for n=200).1 <= time_i <= 10^9 → A path can have up to 199 edges, so the total distance can reach roughly 2 x 10^11, which overflows a 32-bit int. Distances must use 64-bit integers.modulo 10^9 + 7 → The path count can grow large, so we take it modulo a prime.Find all possible paths from node 0 to node n-1, compute each path's total weight, find the minimum weight, and count how many paths have that minimum weight.
We enumerate paths using DFS. Starting from node 0, we explore every route to node n-1, tracking the accumulated distance. After exploring all paths, we know the shortest distance and can count how many paths match it.
This is correct but wildly inefficient. The number of simple paths in a graph can be exponential (factorial in the worst case for dense graphs). With n up to 200, this is completely impractical.
Enumerating every path is exponentially slow, and shared subpaths get recomputed across routes. The next approach computes the shortest distance to each node once and accumulates the path counts during a single graph traversal.
Dijkstra's algorithm processes nodes in order of their shortest distance from the source. When we pop a node from the priority queue, its final shortest distance is fixed. We can attach a counting mechanism on top of that.
We maintain two arrays: dist[i] for the shortest distance from node 0 to node i, and ways[i] for the number of shortest paths from node 0 to node i. Initially dist[0] = 0 and ways[0] = 1, since there is one path from the start to itself, the empty path.
When we relax an edge from node u to node v with weight w, three things can happen:
dist[u] + w < dist[v]. We update dist[v] and set ways[v] = ways[u], because all previous paths to v are no longer shortest, and the only shortest paths to v now come through u.dist[u] + w == dist[v]. We add ways[u] to ways[v], because we discovered additional shortest paths that go through u.dist[u] + w > dist[v]. We ignore it completely.The argument rests on the order in which Dijkstra finalizes nodes. When node u is popped from the heap, dist[u] is final: any path reaching u through a node still in the heap would have distance at least dist[u], so no shorter path to u remains. Because every shortest path to u arrives through some neighbor that was popped earlier, and each of those neighbors already relaxed its edge into u, ways[u] has accumulated the counts of all of them. So ways[u] is also final at pop time.
This is why using ways[u] during relaxation is safe. A strictly shorter path to v discards v's old paths and replaces them with the paths through u, so ways[v] = ways[u]. An equal-distance path adds a new set of shortest paths through u, so ways[v] += ways[u]. One requirement: edge weights are positive (time_i >= 1), which guarantees u is finalized before any node it relaxes, so no count is read before it is complete.
dist array with infinity and ways array with 0. Set dist[0] = 0 and ways[0] = 1.(0, 0) (distance, node) into a min-heap.(d, u).d > dist[u], skip (stale entry).dist[u] + w < dist[v]: update dist[v] = dist[u] + w, set ways[v] = ways[u], push to heap.dist[u] + w == dist[v]: add ways[u] to ways[v] (modulo 10^9+7).ways[n-1].