1514. Path with Maximum Probability
Description
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^4
0 <= start, end < n
start != end
0 <= a, b < n
a != b
0 <= succProb.length == edges.length <= 2*10^4
0 <= succProb[i] <= 1
- There is at most one edge between every two nodes.
Solutions
Solution 1: Heap-Optimized Dijkstra Algorithm
We can use Dijkstra's algorithm to find the shortest path, but here we modify it slightly to find the path with the maximum probability.
We use a priority queue (max-heap) $\textit{pq}$ to store the probability from the starting point to each node and the node's identifier. Initially, we set the probability of the starting point to $1$ and the probabilities of the other nodes to $0$, then add the starting point to $\textit{pq}$.
In each iteration, we take out the node $a$ with the highest probability from $\textit{pq}$ and its probability $w$. If the probability of node $a$ is already greater than $w$, we can skip this node. Otherwise, we traverse all adjacent edges $(a, b)$ of $a$. If the probability of $b$ is less than the probability of $a$ multiplied by the probability of $(a, b)$, we update the probability of $b$ and add $b$ to $\textit{pq}$.
Finally, we obtain the maximum probability from the starting point to the endpoint.
The time complexity is $O(m \times \log m)$, and the space complexity is $O(m)$. Here, $m$ is the number of edges.
Python3
class Solution:
def maxProbability(
self,
n: int,
edges: List[List[int]],
succProb: List[float],
start_node: int,
end_node: int,
) -> float:
g: List[List[Tuple[int, float]]] = [[] for _ in range(n)]
for (a, b), p in zip(edges, succProb):
g[a].append((b, p))
g[b].append((a, p))
pq = [(-1, start_node)]
dist = [0] * n
dist[start_node] = 1
while pq:
w, a = heappop(pq)
w = -w
if dist[a] > w:
continue
for b, p in g[a]:
if (t := w * p) > dist[b]:
dist[b] = t
heappush(pq, (-t, b))
return dist[end_node]
Java
class Solution {
public double maxProbability(
int n, int[][] edges, double[] succProb, int start_node, int end_node) {
List<Pair<Integer, Double>>[] g = new List[n];
Arrays.setAll(g, k -> new ArrayList<>());
for (int i = 0; i < edges.length; ++i) {
var e = edges[i];
int a = e[0], b = e[1];
double p = succProb[i];
g[a].add(new Pair<>(b, p));
g[b].add(new Pair<>(a, p));
}
double[] dist = new double[n];
dist[start_node] = 1;
PriorityQueue<Pair<Integer, Double>> pq
= new PriorityQueue<>(Comparator.comparingDouble(p -> - p.getValue()));
pq.offer(new Pair<>(start_node, 1.0));
while (!pq.isEmpty()) {
var p = pq.poll();
int a = p.getKey();
double w = p.getValue();
if (dist[a] > w) {
continue;
}
for (var e : g[a]) {
int b = e.getKey();
double pab = e.getValue();
double wab = w * pab;
if (wab > dist[b]) {
dist[b] = wab;
pq.offer(new Pair<>(b, wab));
}
}
}
return dist[end_node];
}
}
C++
class Solution {
public:
double maxProbability(int n, vector<vector<int>>& edges, vector<double>& succProb, int start_node, int end_node) {
using pdi = pair<double, int>;
vector<pdi> g[n];
for (int i = 0; i < edges.size(); ++i) {
int a = edges[i][0], b = edges[i][1];
double p = succProb[i];
g[a].emplace_back(p, b);
g[b].emplace_back(p, a);
}
vector<double> dist(n);
dist[start_node] = 1;
priority_queue<pdi> pq;
pq.emplace(1, start_node);
while (!pq.empty()) {
auto [w, a] = pq.top();
pq.pop();
if (dist[a] > w) {
continue;
}
for (auto [p, b] : g[a]) {
auto nw = w * p;
if (nw > dist[b]) {
dist[b] = nw;
pq.emplace(nw, b);
}
}
}
return dist[end_node];
}
};
Go
func maxProbability(n int, edges [][]int, succProb []float64, start_node int, end_node int) float64 {
g := make([][]pair, n)
for i, e := range edges {
a, b := e[0], e[1]
p := succProb[i]
g[a] = append(g[a], pair{p, b})
g[b] = append(g[b], pair{p, a})
}
pq := hp{{1, start_node}}
dist := make([]float64, n)
dist[start_node] = 1
for len(pq) > 0 {
p := heap.Pop(&pq).(pair)
w, a := p.p, p.a
if dist[a] > w {
continue
}
for _, e := range g[a] {
b, p := e.a, e.p
if nw := w * p; nw > dist[b] {
dist[b] = nw
heap.Push(&pq, pair{nw, b})
}
}
}
return dist[end_node]
}
type pair struct {
p float64
a int
}
type hp []pair
func (h hp) Len() int { return len(h) }
func (h hp) Less(i, j int) bool { return h[i].p > h[j].p }
func (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(x any) { *h = append(*h, x.(pair)) }
func (h *hp) Pop() (x any) { a := *h; x = a[len(a)-1]; *h = a[:len(a)-1]; return }
TypeScript
function maxProbability(
n: number,
edges: number[][],
succProb: number[],
start_node: number,
end_node: number,
): number {
const pq = new MaxPriorityQueue({ priority: v => v[0] });
const g: [number, number][][] = Array.from({ length: n }, () => []);
for (let i = 0; i < edges.length; ++i) {
const [a, b] = edges[i];
g[a].push([b, succProb[i]]);
g[b].push([a, succProb[i]]);
}
const dist = Array.from({ length: n }, () => 0);
dist[start_node] = 1;
pq.enqueue([1, start_node]);
while (!pq.isEmpty()) {
const [w, a] = pq.dequeue().element;
if (dist[a] > w) {
continue;
}
for (const [b, p] of g[a]) {
const nw = w * p;
if (nw > dist[b]) {
dist[b] = nw;
pq.enqueue([nw, b]);
}
}
}
return dist[end_node];
}