You are in a city that consists of n
intersections numbered from 0
to n - 1
with bi-directional roads between some intersections. The inputs are generated such that you can reach any intersection from any other intersection and that there is at most one road between any two intersections.
You are given an integer n
and a 2D integer array roads
where roads[i] = [u<sub>i</sub>, v<sub>i</sub>, time<sub>i</sub>]
means that there is a road between intersections u<sub>i</sub>
and v<sub>i</sub>
that takes time<sub>i</sub>
minutes to travel. You want to know in how many ways you can travel from intersection 0
to intersection n - 1
in the shortest amount of time.
Return the number of ways you can arrive at your destination in the shortest amount of time. Since the answer may be large, return it modulo 10<sup>9</sup> + 7
.
Example 1:
Imagine a city with 7 intersections (0 to 6). The roads are defined as follows: [[0,6,7],[0,1,2],[1,2,3],[1,3,3],[6,3,3],[3,5,1],[6,5,1],[2,5,1],[0,4,5],[4,6,2]]
. Here, for example, [0,6,7]
means there is a road between intersection 0 and intersection 6 that takes 7 minutes. What are the number of ways to get from intersection 0 to intersection 6 in the shortest amount of time?
Example 2:
Consider a city with 2 intersections (0 and 1). There is one road between them: [[1,0,10]]
. What is the number of ways to get from intersection 0 to intersection 1 in the shortest time?
A brute force approach would involve exploring all possible paths from the starting intersection to the destination intersection. We could use a recursive depth-first search (DFS) to enumerate all paths and calculate the time taken for each path. We'd keep track of the shortest time found so far and the number of paths that achieve that shortest time.
This approach, however, suffers from exponential time complexity as the number of paths can grow very quickly with the number of intersections and roads. It is also prone to stack overflow errors for larger graphs because of the recursive nature of DFS.
A more efficient solution can be achieved using Dijkstra's algorithm to find the shortest paths and simultaneously count the number of shortest paths. Dijkstra's algorithm is a graph search algorithm that solves the single-source shortest path problem for a graph with non-negative edge weights, producing a shortest-path tree.
Here's how we can adapt Dijkstra's algorithm:
Initialization:
dist[i]
stores the shortest distance from the starting intersection (0) to intersection i
. Initialize dist[0]
to 0 and all other dist[i]
to infinity.ways[i]
stores the number of shortest paths from the starting intersection (0) to intersection i
. Initialize ways[0]
to 1 and all other ways[i]
to 0.Iteration:
u
with the smallest distance from the priority queue.v
of u
:
dist[u] + time(u, v) < dist[v]
:
dist[v] = dist[u] + time(u, v)
. This means we found a shorter path to v
.ways[v] = ways[u]
. The number of shortest paths to v
is now the same as the number of shortest paths to u
.v
to the priority queue.dist[u] + time(u, v) == dist[v]
:
ways[v]
by ways[u]
. This means we found another shortest path to v
. We add the number of shortest paths to u
because any shortest path to u
can be extended to v
with the same total distance.Result:
ways[n-1]
will contain the number of shortest paths from intersection 0 to intersection n-1
.10^9 + 7
to avoid integer overflow.n = 1
, there's only one intersection, and the number of ways is 1.roads
is empty and n > 1
, there is no path from 0 to n-1
, although the problem states that all intersections are reachable. Ensure this constraint holds during input validation.time
values in roads
can be large, so use long
datatype in the priority queue. Also use long
type for storing intermediate distances to avoid integer overflow.import java.util.*;
class Solution {
public int countPaths(int n, int[][] roads) {
List<List<Pair>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) {
adj.add(new ArrayList<>());
}
for (int[] road : roads) {
int u = road[0];
int v = road[1];
int time = road[2];
adj.get(u).add(new Pair(v, time));
adj.get(v).add(new Pair(u, time));
}
long[] dist = new long[n];
long[] ways = new long[n];
Arrays.fill(dist, Long.MAX_VALUE);
dist[0] = 0;
ways[0] = 1;
PriorityQueue<Pair> pq = new PriorityQueue<>(Comparator.comparingLong(p -> p.time));
pq.offer(new Pair(0, 0));
int MOD = (int) 1e9 + 7;
while (!pq.isEmpty()) {
Pair curr = pq.poll();
int u = curr.node;
long d = curr.time;
if (d > dist[u]) continue; // Optimization: Skip if we've already found a shorter path to u
for (Pair neighbor : adj.get(u)) {
int v = neighbor.node;
long time = neighbor.time;
if (dist[u] + time < dist[v]) {
dist[v] = dist[u] + time;
ways[v] = ways[u];
pq.offer(new Pair(v, dist[v]));
} else if (dist[u] + time == dist[v]) {
ways[v] = (ways[v] + ways[u]) % MOD;
}
}
}
return (int) ways[n - 1];
}
static class Pair {
int node;
long time;
public Pair(int node, long time) {
this.node = node;
this.time = time;
}
}
}
dist
and ways
arrays, and O(E) space to store the adjacency list.