There is a directed weighted graph that consists of n
nodes numbered from 0
to n - 1
. The edges of the graph are initially represented by the given array edges
where edges[i] = [fromi, toi, edgeCosti]
meaning that there is an edge from fromi
to toi
with the cost edgeCosti
.
Implement the Graph
class:
Graph(int n, int[][] edges)
initializes the object with n
nodes and the given edges.addEdge(int[] edge)
adds an edge to the list of edges where edge = [from, to, edgeCost]
. It is guaranteed that there is no edge between the two nodes before adding this one.int shortestPath(int node1, int node2)
returns the minimum cost of a path from node1
to node2
. If no path exists, return -1
. The cost of a path is the sum of the costs of the edges in the path.Example 1:
Input ["Graph", "shortestPath", "shortestPath", "addEdge", "shortestPath"] [[4, [[0, 2, 5], [0, 1, 2], [1, 2, 1], [3, 0, 3]]], [3, 2], [0, 3], [[1, 3, 4]], [0, 3]] Output [null, 6, -1, null, 6] Explanation Graph g = new Graph(4, [[0, 2, 5], [0, 1, 2], [1, 2, 1], [3, 0, 3]]); g.shortestPath(3, 2); // return 6. The shortest path from 3 to 2 in the first diagram above is 3 -> 0 -> 1 -> 2 with a total cost of 3 + 2 + 1 = 6. g.shortestPath(0, 3); // return -1. There is no path from 0 to 3. g.addEdge([1, 3, 4]); // We add an edge from node 1 to node 3, and we get the second diagram above. g.shortestPath(0, 3); // return 6. The shortest path from 0 to 3 now is 0 -> 1 -> 3 with a total cost of 2 + 4 = 6.
Constraints:
1 <= n <= 100
0 <= edges.length <= n * (n - 1)
edges[i].length == edge.length == 3
0 <= fromi, toi, from, to, node1, node2 <= n - 1
1 <= edgeCosti, edgeCost <= 106
100
calls will be made for addEdge
.100
calls will be made for shortestPath
.When 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:
To find the shortest path in a graph using brute force, we explore every possible route between the starting point and the destination. We calculate the length of each route and then choose the shortest one. This method guarantees finding the shortest path if it exists, but can take a very long time for larger graphs.
Here's how the algorithm would work step-by-step:
def find_shortest_path_brute_force(graph, start_node, end_node):
all_possible_paths = list_all_paths(graph, start_node, end_node)
shortest_path = None
shortest_distance = float('inf')
for path in all_possible_paths:
current_path_distance = calculate_path_distance(graph, path)
# Check if current path is the shortest
if current_path_distance < shortest_distance:
shortest_distance = current_path_distance
shortest_path = path
return shortest_path
def list_all_paths(graph, start_node, end_node, path=None):
if path is None:
path = [start_node]
if start_node == end_node:
return [path]
if start_node not in graph:
return []
paths = []
for neighbor in graph[start_node]:
if neighbor not in path:
new_path = path + [neighbor]
new_paths = list_all_paths(graph, neighbor, end_node, new_path)
paths.extend(new_paths)
return paths
def calculate_path_distance(graph, path):
total_distance = 0
for i in range(len(path) - 1):
current_node = path[i]
next_node = path[i + 1]
# Ensure edge exists to avoid errors
if next_node not in graph[current_node]:
return float('inf')
total_distance += graph[current_node][next_node]
return total_distance
#Example graph representation
#graph = {
# 'A': {'B': 5, 'C': 2},
# 'B': {'D': 4},
# 'C': {'B': 8, 'D': 7},
# 'D': {}
#}
#
#start_node = 'A'
#end_node = 'D'
#
#shortest_path = find_shortest_path_brute_force(graph, start_node, end_node)
#
#if shortest_path:
# print("Shortest path:", shortest_path)
#else:
# print("No path found.")
To efficiently design a graph with shortest path calculations, we'll use a well-known algorithm optimized for finding the quickest route between any two points. We'll start by creating the graph and then leverage this algorithm to answer shortest path queries efficiently, avoiding recalculating paths every time.
Here's how the algorithm would work step-by-step:
import heapq
class Graph:
def __init__(self, number_of_nodes):
self.number_of_nodes = number_of_nodes
self.adjacency_list = [[] for _ in range(number_of_nodes)]
def add_edge(self, node1, node2, weight):
self.adjacency_list[node1].append((node2, weight))
class GraphWithShortestPaths:
def __init__(self, number_of_nodes):
self.graph = Graph(number_of_nodes)
self.shortest_paths = {}
def add_edge(self, node1, node2, weight):
self.graph.add_edge(node1, node2, weight)
def precompute_shortest_paths(self):
# Precompute shortest paths between all pairs of nodes.
for start_node in range(self.graph.number_of_nodes):
self.shortest_paths[start_node] = self.dijkstra(start_node)
def dijkstra(self, start_node):
# Implement Dijkstra's algorithm for single-source shortest paths.
distances = {node: float('inf') for node in range(self.graph.number_of_nodes)}
distances[start_node] = 0
priority_queue = [(0, start_node)]
while priority_queue:
distance, current_node = heapq.heappop(priority_queue)
if distance > distances[current_node]:
continue
for neighbor, weight in self.graph.adjacency_list[current_node]:
new_distance = distance + weight
# Update 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
def get_shortest_path(self, node1, node2):
# Retrieve precomputed shortest path length.
if not self.shortest_paths:
self.precompute_shortest_paths()
# Return infinity if there is no path
if self.shortest_paths[node1][node2] == float('inf'):
return -1
return self.shortest_paths[node1][node2]
Case | How to Handle |
---|---|
Null or empty edges list in constructor. | Initialize the graph with no edges if the input edges list is null or empty. |
n is zero or negative. | Raise an IllegalArgumentException since the number of nodes should be a positive integer. |
Edges contain negative costs. | Raise an IllegalArgumentException as Dijkstra's algorithm requires non-negative edge weights. |
addEdge called with negative cost. | Raise an IllegalArgumentException since negative costs are not allowed. |
addEdge called with nodes outside the range [0, n-1]. | Raise an IllegalArgumentException because node indices must be within the valid range. |
shortestPath called with start or end nodes outside the range [0, n-1]. | Return -1 since the start and end nodes must be valid within the graph. |
No path exists between start and end nodes in shortestPath. | Dijkstra's algorithm should naturally return -1 if the end node is never reached, indicating no path exists. |
Graph contains cycles. | Dijkstra's algorithm handles cycles correctly by always choosing the shortest known path, effectively ignoring longer cyclical paths. |