There is an undirected weighted connected graph. You are given a positive integer n which denotes that the graph has n nodes labeled from 1 to n, and an array edges where each edges[i] = [ui, vi, weighti] denotes that there is an edge between nodes ui and vi with weight equal to weighti.
A path from node start to node end is a sequence of nodes [z0, z1, z2, ..., zk] such that z0 = start and zk = end and there is an edge between zi and zi+1 where 0 <= i <= k-1.
The distance of a path is the sum of the weights on the edges of the path. Let distanceToLastNode(x) denote the shortest distance of a path between node n and node x. A restricted path is a path that also satisfies that distanceToLastNode(zi) > distanceToLastNode(zi+1) where 0 <= i <= k-1.
Return the number of restricted paths from node 1 to node n. Since that number may be too large, return it modulo 109 + 7.
Example 1:
Input: n = 5, edges = [[1,2,3],[1,3,3],[2,3,1],[1,4,2],[5,2,2],[3,5,1],[5,4,10]]
Output: 3
Explanation: Each circle contains the node number in black and its distanceToLastNode value in blue. The three restricted paths are:
1) 1 --> 2 --> 5
2) 1 --> 2 --> 3 --> 5
3) 1 --> 3 --> 5
Example 2:
Input: 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]]
Output: 1
Explanation: Each circle contains the node number in black and its distanceToLastNode value in blue. The only restricted path is 1 --> 3 --> 7.
Constraints:
1 <= n <= 2 * 104n - 1 <= edges.length <= 4 * 104edges[i].length == 31 <= ui, vi <= nui != vi1 <= weighti <= 105When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach to finding restricted paths involves exploring every possible route from the starting point to the destination. We systematically check each path to see if it meets the 'restricted' criteria. This is like trying every road on a map until we find the ones that get us where we need to go and follow the rules.
Here's how the algorithm would work step-by-step:
def number_of_restricted_paths(number_of_nodes, edges):
graph = [[] for _ in range(number_of_nodes + 1)]
for start_node, end_node, weight in edges:
graph[start_node].append((end_node, weight))
graph[end_node].append((start_node, weight))
distance = [float('inf')] * (number_of_nodes + 1)
distance[number_of_nodes] = 0
import heapq
priority_queue = [(0, number_of_nodes)]
while priority_queue:
dist, node = heapq.heappop(priority_queue)
if dist > distance[node]:
continue
for neighbor, weight in graph[node]:
if distance[neighbor] > distance[node] + weight:
distance[neighbor] = distance[node] + weight
heapq.heappush(priority_queue, (distance[neighbor], neighbor))
restricted_paths_count = 0
def depth_first_search(current_node, visited):
nonlocal restricted_paths_count
if current_node == number_of_nodes:
restricted_paths_count = (restricted_paths_count + 1) % (10**9 + 7)
return
for neighbor, _ in graph[current_node]:
# Only explore neighbors that satisfy the restricted path condition
if distance[current_node] > distance[neighbor]:
depth_first_search(neighbor, visited | {neighbor})
# Start the DFS from the first node
depth_first_search(1, {1})
return restricted_paths_countThe goal is to count the number of special routes from the beginning to the end of a map. We'll efficiently explore the map by focusing on paths where each step gets closer to the end, avoiding unnecessary backtracking.
Here's how the algorithm would work step-by-step:
import heapq
def countRestrictedPaths(number_of_nodes, edges):
graph = [[] for _ in range(number_of_nodes + 1)]
for node_a, node_b, weight in edges:
graph[node_a].append((node_b, weight))
graph[node_b].append((node_a, weight))
distance_from_end = dijkstra(number_of_nodes, graph)
number_of_paths = [0] * (number_of_nodes + 1)
number_of_paths[number_of_nodes] = 1
def dfs(current_node):
if number_of_paths[current_node] != 0:
return number_of_paths[current_node]
paths = 0
for neighbor, _ in graph[current_node]:
# Visit only nodes closer to the end
if distance_from_end[neighbor] < distance_from_end[current_node]:
paths = (paths + dfs(neighbor)) % (10**9 + 7)
number_of_paths[current_node] = paths
return paths
# Begin traversal from the start node
return dfs(1)
def dijkstra(number_of_nodes, graph):
distances = [float('inf')] * (number_of_nodes + 1)
distances[number_of_nodes] = 0
priority_queue = [(0, number_of_nodes)]
while priority_queue:
distance, current_node = heapq.heappop(priority_queue)
if distance > distances[current_node]:
continue
for neighbor, weight in graph[current_node]:
new_distance = distance + weight
# Update shortest distance if a shorter path is found
if new_distance < distances[neighbor]:
distances[neighbor] = new_distance
heapq.heappush(priority_queue, (new_distance, neighbor))
return distances| Case | How to Handle |
|---|---|
| Empty graph (no edges) | Return 0 if the graph has no edges as there are no paths. |
| Single node graph | If the graph consists of a single node, the path count is 1 (the node itself). |
| Graph with no path between start and end nodes | The algorithm should return 0 when there is no path from the first to the last node by resulting in an empty priority queue after Dijkstra's algorithm. |
| Graph with cycles | Dijkstra's algorithm and dynamic programming inherently handle cycles as we only consider nodes with a smaller distance to the target node preventing infinite loops. |
| Large graph (many nodes and edges) | The algorithm needs to be efficient, using Dijkstra's algorithm with a priority queue for performance, and dynamic programming to avoid recomputation. |
| Weights causing integer overflow during distance calculation | Use a larger data type like long to store distances to prevent integer overflow during distance calculations. |
| Graph where all edges have the same weight | Dijkstra's still works correctly but the priority queue might degrade in performance compared to graphs with diverse edge weights. |
| The last node is unreachable during distance calculation | If the distance to the last node is infinity after Dijkstra, it means there are no paths, return 0. |