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
.
For example:
n = 7, roads = [[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]]
In this case the output is 4. The shortest amount of time it takes to go from intersection 0 to intersection 6 is 7 minutes. The four ways to get there in 7 minutes are:
Another example:
n = 2, roads = [[1,0,10]]
In this case, the output is 1 because There is only one way to go from intersection 0 to intersection 1, and it takes 10 minutes. How would you approach this problem and what would be the most efficient solution?
A brute-force approach to solve this problem would involve exploring all possible paths from the starting intersection (0) to the destination intersection (n-1) and keeping track of the shortest time taken to reach the destination. Then, we count the number of paths that achieve this shortest time.
This approach is highly inefficient because it explores all possible paths, which can grow exponentially with the number of intersections and roads.
Big O Runtime: O(V^E) where V is number of vertices and E is number of edges, due to exploring all possible paths. Big O Space Usage: O(V), due to the depth of the call stack in DFS or the size of the queue in BFS.
A more efficient solution involves using Dijkstra's algorithm to find the shortest time from the starting intersection to all other intersections. Simultaneously, we maintain a count of the number of shortest paths to each intersection.
dist[i]
= shortest time from intersection 0 to intersection i (initialized to infinity for all i except 0, which is initialized to 0).ways[i]
= number of shortest paths from intersection 0 to intersection i (initialized to 0 for all i except 0, which is initialized to 1).dist[i]
).u
with the smallest dist[u]
from the priority queue.v
of u
:
dist[u] + time(u, v) < dist[v]
:
dist[v] = dist[u] + time(u, v)
.ways[v] = ways[u]
(reset the path count, as we've found a shorter path).v
to the priority queue.dist[u] + time(u, v) == dist[v]
:
ways[v] = (ways[v] + ways[u]) % (10^9 + 7)
(add the number of paths to u
to the number of paths to v
).ways[n-1]
contains the number of shortest paths from intersection 0 to intersection n-1.Edge Cases:
n
is 1, return 1.Big O Runtime: O(E log V), where V is the number of intersections and E is the number of roads. This is due to Dijkstra's algorithm using a priority queue.
Big O Space Usage: O(V + E), where V is the number of intersections (for dist
and ways
arrays) and E is the number of roads (for the adjacency list representation of the graph).
import heapq
def count_paths(n: int, roads: list[list[int]]) -> int:
adj = [[] for _ in range(n)]
for u, v, time in roads:
adj[u].append((v, time))
adj[v].append((u, time))
dist = [float('inf')] * n
ways = [0] * n
dist[0] = 0
ways[0] = 1
pq = [(0, 0)] # (distance, node)
MOD = 10**9 + 7
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, time in adj[u]:
if dist[u] + time < dist[v]:
dist[v] = dist[u] + time
ways[v] = ways[u]
heapq.heappush(pq, (dist[v], v))
elif dist[u] + time == dist[v]:
ways[v] = (ways[v] + ways[u]) % MOD
return ways[n - 1]