You are given an undirected weighted graph of n nodes (0-indexed), represented by an edge list where edges[i] = [a, b] is an undirected edge connecting the nodes a and b with a probability of success of traversing that edge succProb[i].
Given two nodes start and end, find the path with the maximum probability of success to go from start to end and return its success probability.
If there is no path from start to end, return 0. Your answer will be accepted if it differs from the correct answer by at most 1e-5.
Example 1:

Input: n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.2], start = 0, end = 2 Output: 0.25000 Explanation: There are two paths from start to end, one having a probability of success = 0.2 and the other has 0.5 * 0.5 = 0.25.
Example 2:

Input: n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.3], start = 0, end = 2 Output: 0.30000
Example 3:

Input: n = 3, edges = [[0,1]], succProb = [0.5], start = 0, end = 2 Output: 0.00000 Explanation: There is no path between 0 and 2.
Constraints:
2 <= n <= 10^40 <= start, end < nstart != end0 <= a, b < na != b0 <= succProb.length == edges.length <= 2*10^40 <= succProb[i] <= 1When 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 strategy explores every single possible route between the starting point and the destination. For each of these routes, we compute its probability. Finally, we pick the route that has the highest probability.
Here's how the algorithm would work step-by-step:
def path_with_maximum_probability_brute_force(
number_of_nodes,
edges,
probabilities,
start_node,
end_node,
):
graph = [[] for _ in range(number_of_nodes)]
for i in range(len(edges)):
node_a, node_b = edges[i]
probability = probabilities[i]
graph[node_a].append((node_b, probability))
graph[node_b].append((node_a, probability))
maximum_probability = 0.0
def depth_first_search(
current_node, current_probability, visited_nodes
):
nonlocal maximum_probability
# If we reach the end node, update the max probability
if current_node == end_node:
maximum_probability = max(
maximum_probability, current_probability
)
return
# Prevent cycles by tracking visited nodes
visited_nodes.add(current_node)
for neighbor_node, edge_probability in graph[current_node]:
if neighbor_node not in visited_nodes:
new_probability = current_probability * edge_probability
depth_first_search(
neighbor_node,
new_probability,
set(visited_nodes),
)
depth_first_search(start_node, 1.0, set())
return maximum_probabilityWe're trying to find the path with the highest probability in a network. The best way to do this is to use a method that explores the network gradually, always prioritizing paths that seem most promising so far. This prevents us from wasting time on paths that are unlikely to lead to the best result.
Here's how the algorithm would work step-by-step:
import heapq
def path_with_maximum_probability(number_of_nodes: int, edges: list[list[int]], probabilities: list[float], start_node: int, end_node: int) -> float:
adjacency_list = [[] for _ in range(number_of_nodes)]
for i in range(len(edges)):
source_node, destination_node = edges[i]
edge_probability = probabilities[i]
adjacency_list[source_node].append((destination_node, edge_probability))
adjacency_list[destination_node].append((source_node, edge_probability))
max_probability = [0.0] * number_of_nodes
max_probability[start_node] = 1.0
# Use a max-heap to prioritize nodes with higher probabilities.
priority_queue = [(-1.0, start_node)]
while priority_queue:
current_probability, current_node = heapq.heappop(priority_queue)
current_probability = -current_probability
# If the current probability is less than the max probability, skip.
if current_probability < max_probability[current_node]:
continue
# Iterate through neighbors of the current node.
for neighbor_node, edge_probability in adjacency_list[current_node]:
new_probability = current_probability * edge_probability
# Update probability if a higher one is found.
if new_probability > max_probability[neighbor_node]:
max_probability[neighbor_node] = new_probability
# Negate probability for max-heap.
heapq.heappush(priority_queue, (-new_probability, neighbor_node))
# Returns the probability of reaching the end node.
return max_probability[end_node]| Case | How to Handle |
|---|---|
| Empty graph (n=0 or empty edges list) | Return 0.0 as there is no path. |
| Start and end node are the same | Return 1.0, as the probability of being at the start node is always 1. |
| No path exists between start and end nodes | Return 0.0 after Dijkstra's algorithm completes without reaching the end node. |
| Graph contains cycles | Dijkstra's algorithm with a priority queue handles cycles by continuously updating probabilities. |
| Edge probabilities are zero | Zero probability edges effectively disconnect nodes; Dijkstra's handles this automatically by not traversing them if a better path exists. |
| Large number of nodes and edges (scalability) | Dijkstra's algorithm with a priority queue provides efficient logarithmic time complexity for node selection, allowing it to scale efficiently. |
| Floating point precision issues causing incorrect comparisons | Use a small epsilon value for comparing floating point numbers in the priority queue and termination condition. |
| Negative edge probabilities (invalid input) | Throw an exception or return an error code, as probabilities cannot be negative. |