1514. Path with Maximum Probability (M)
https://leetcode.com/problems/path-with-maximum-probability/
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.
Solution:
我说这题一看就是 Dijkstra 算法,但聪明的你肯定会反驳我:
1、这题给的是无向图,也可以用 Dijkstra 算法吗?
2、更重要的是,Dijkstra 算法计算的是最短路径,计算的是最小值,这题让你计算最大概率是一个最大值,怎么可能用 Dijkstra 算法呢?
问得好!
首先关于有向图和无向图,前文 图算法基础 说过,无向图本质上可以认为是「双向图」,从而转化成有向图。
重点说说最大值和最小值这个问题,其实 Dijkstra 和很多最优化算法一样,计算的是「最优值」,这个最优值可能是最大值,也可能是最小值。
标准 Dijkstra 算法是计算最短路径的,但你有想过为什么 Dijkstra 算法不允许存在负权重边么?
因为 Dijkstra 计算最短路径的正确性依赖一个前提:路径中每增加一条边,路径的总权重就会增加。
这个前提的数学证明大家有兴趣可以自己搜索一下,我这里只说结论,其实你把这个结论反过来也是 OK 的:
如果你想计算最长路径,路径中每增加一条边,路径的总权重就会减少,要是能够满足这个条件,也可以用 Dijkstra 算法。
你看这道题是不是符合这个条件?边和边之间是乘法关系,每条边的概率都是小于 1 的,所以肯定会越乘越小。
只不过,这道题的解法要把优先级队列的排序顺序反过来,一些 if 大小判断也要反过来,我们直接看解法代码吧:
double maxProbability(int n, int[][] edges, double[] succProb, int start, int end) {
List<double[]>[] graph = new LinkedList[n];
for (int i = 0; i < n; i++) {
graph[i] = new LinkedList<>();
}
// 构造邻接表结构表示图
for (int i = 0; i < edges.length; i++) {
int from = edges[i][0];
int to = edges[i][1];
double weight = succProb[i];
// 无向图就是双向图;先把 int 统一转成 double,待会再转回来
graph[from].add(new double[]{(double)to, weight});
graph[to].add(new double[]{(double)from, weight});
}
return dijkstra(start, end, graph);
}
class State {
// 图节点的 id
int id;
// 从 start 节点到达当前节点的概率
double probFromStart;
State(int id, double probFromStart) {
this.id = id;
this.probFromStart = probFromStart;
}
}
double dijkstra(int start, int end, List<double[]>[] graph) {
// 定义:probTo[i] 的值就是节点 start 到达节点 i 的最大概率
double[] probTo = new double[graph.length];
// dp table 初始化为一个取不到的最小值
Arrays.fill(probTo, -1);
// base case,start 到 start 的概率就是 1
probTo[start] = 1;
// 优先级队列,probFromStart 较大的排在前面
Queue<State> pq = new PriorityQueue<>((a, b) -> {
return Double.compare(b.probFromStart, a.probFromStart);
});
// 从起点 start 开始进行 BFS
pq.offer(new State(start, 1));
while (!pq.isEmpty()) {
State curState = pq.poll();
int curNodeID = curState.id;
double curProbFromStart = curState.probFromStart;
// 遇到终点提前返回
if (curNodeID == end) {
return curProbFromStart;
}
if (curProbFromStart < probTo[curNodeID]) {
// 已经有一条概率更大的路径到达 curNode 节点了
continue;
}
// 将 curNode 的相邻节点装入队列
for (double[] neighbor : graph[curNodeID]) {
int nextNodeID = (int)neighbor[0];
// 看看从 curNode 达到 nextNode 的概率是否会更大
double probToNextNode = probTo[curNodeID] * neighbor[1];
if (probTo[nextNodeID] < probToNextNode) {
probTo[nextNodeID] = probToNextNode;
pq.offer(new State(nextNodeID, probToNextNode));
}
}
}
// 如果到达这里,说明从 start 开始无法到达 end,返回 0
return 0.0;
}
好了,到这里本文就结束了,总共 6000 多字,这三道例题都是比较困难的,如果你能够看到这里,真得给你鼓掌。
其实前文 毕业旅行省钱算法 中讲过限制之下的最小路径问题,当时是使用动态规划思路解决的,但文末也给了 Dijkstra 算法代码,仅仅在本文模板的基础上做了一些变换,你理解本文后可以对照着去看看那道题目。
最后还是那句话,做题在质不在量,希望大家能够透彻理解最基本的数据结构,以不变应万变。
Last updated
Was this helpful?