|
1 | 1 | # [Problem 1514: Path with Maximum Probability](https://leetcode.com/problems/path-with-maximum-probability/description/?envType=daily-question)
|
2 | 2 |
|
3 | 3 | ## Initial thoughts (stream-of-consciousness)
|
| 4 | +- First we need a way to represent the graph more conveniently. I'm thinking we should have a hash table whose keys are nodes and whose values are lists of connected nodes + probabilities. E.g., for the first given example, `graph[0] = [(2, 0.2), (1, 0.5)]`, etc. We can build up this hash table with a single pass through the edge list + success probability list: |
| 5 | +```python |
| 6 | +graph = {} |
| 7 | +for edge, prob in zip(edges, succProb): |
| 8 | + if edge[0] in graph: |
| 9 | + graph[edge[0]].append((edge[1], prob)) |
| 10 | + else: |
| 11 | + graph[edge[0]] = [(edge[1], prob)] |
| 12 | + |
| 13 | + if edge[1] in graph: |
| 14 | + graph[edge[1]].append((edge[0], prob)) |
| 15 | + else: |
| 16 | + graph[edge[1]] = [(edge[0], prob)] |
| 17 | +``` |
| 18 | +- Now I think we can build up paths using BFS or DFS starting from `start_node`: |
| 19 | + - If we hit a loop, stop and delete that path from the queue/stack |
| 20 | + - If we reach `end_node`: |
| 21 | + - Go back and compute the probability of this path |
| 22 | + - If it's higher than the probability of the current best path (initialized to `0`), replace the best probability |
| 23 | + - Then delete the path from the queue/stack |
| 24 | +- Potential pitfalls: |
| 25 | + - We might run out of memory if we build up the paths one node at a time. |
| 26 | + - Ideally we could instead just build up something like probability of the path so far + last node reached. But then I'm not sure we'll be able to detect loops... |
| 27 | + - The probabilities could get very small, leading to rounding errors. We could instead track *log* probabilities (summing as we go) and then exponentiate at the end. |
| 28 | +- Instead of BFS/DFS, maybe we want to use a heap to keep the paths sorted by max probability. |
4 | 29 |
|
5 | 30 | ## Refining the problem, round 2 thoughts
|
| 31 | +We could use the built-in `heapq` to do this (according to the documentation `heapq` is a max heap, but we can just multiply the probabilities by -1 so that the most probable paths appear at the "top" of the heap): |
| 32 | + - First push the start of the path (`(-1.0, start_node)`) onto the heap |
| 33 | + - Keep track of `n` probabilities (of reaching each node). Initialize to 0.0, except the start node (initialize to 1.0). |
| 34 | + - While the heap is not empty: |
| 35 | + - Pop the most probable path `(prob, last_node)` |
| 36 | + - If we're at the end node, return `prob` |
| 37 | + - Otherwise: |
| 38 | + - For each neighbor `x` of `last_node` (connected with probability `new_prob`): |
| 39 | + - Probability of path ending at `x` is `-prob * new_prob` |
| 40 | + - If `-prob * new_prob` is greater than `probabilities[x]`: |
| 41 | + - Update `probabilities[x]` -- this ensures we only push more probable paths than we've already found onto the heap. This also avoids loops, since visiting an already-visited node will necessarily result in a lower probability path to that node. |
| 42 | + - Push `(prob * new_prob, x)` on the heap |
| 43 | + - If the heap empties before we found the end, just return 0.0. |
6 | 44 |
|
7 | 45 | ## Attempted solution(s)
|
8 | 46 | ```python
|
9 |
| -class Solution: # paste your code here! |
10 |
| - ... |
| 47 | +import heapq |
| 48 | + |
| 49 | +class Solution: |
| 50 | + def maxProbability(self, n, edges, succProb, start, end): |
| 51 | + # build the graph |
| 52 | + graph = {} |
| 53 | + for x in range(n): |
| 54 | + graph[x] = [] |
| 55 | + for edge, prob in zip(edges, succProb): |
| 56 | + graph[edge[0]].append((edge[1], prob)) |
| 57 | + graph[edge[1]].append((edge[0], prob)) |
| 58 | + |
| 59 | + # max heap |
| 60 | + heap = [(-1.0, start)] |
| 61 | + probabilities = [0.0] * n |
| 62 | + probabilities[start] = 1.0 |
| 63 | + |
| 64 | + while heap: |
| 65 | + p, node = heapq.heappop(heap) |
| 66 | + if node == end: |
| 67 | + return -p |
| 68 | + |
| 69 | + for x, xp in graph[node]: |
| 70 | + new_prob = p * xp # remember: this is negative |
| 71 | + if -new_prob > probabilities[x]: |
| 72 | + probabilities[x] = -new_prob |
| 73 | + heapq.heappush(heap, (new_prob, x)) |
| 74 | + |
| 75 | + # end not found :( |
| 76 | + return 0.0 |
| 77 | +``` |
| 78 | +- Given test cases pass |
| 79 | +- Another example: |
| 80 | +```python |
| 81 | +n = 7 |
| 82 | +edges = [[0,1],[1,2],[2,3],[3,4],[4,5],[5,6]] |
| 83 | +succProb = [0.01, 0.01, 0.01, 0.01, 0.01, 0.1] |
| 84 | +start_node = 0 |
| 85 | +end_node = 6 |
11 | 86 | ```
|
| 87 | +- passes! |
| 88 | +- Ok, submitting! |
| 89 | + |
| 90 | + |
| 91 | + |
| 92 | +Solved! |
| 93 | + |
0 commit comments