1514. 概率最大的路径
难度 中等
给你一个由 n
个节点(下标从 0 开始)组成的无向加权图,该图由一个描述边的列表组成,其中 edges[i] = [a, b]
表示连接节点 a 和 b 的一条无向边,且该边遍历成功的概率为 succProb[i]
。
指定两个节点分别作为起点 start
和终点 end
,请你找出从起点到终点成功概率最大的路径,并返回其成功概率。
如果不存在从 start
到 end
的路径,请 返回 0 。只要答案与标准答案的误差不超过 1e-5 ,就会被视作正确答案。
示例 1:
输入:n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.2], start = 0, end = 2
输出:0.25000
解释:从起点到终点有两条路径,其中一条的成功概率为 0.2 ,而另一条为 0.5 * 0.5 = 0.25
示例 2:
输入:n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.3], start = 0, end = 2
输出:0.30000
示例 3:
输入:n = 3, edges = [[0,1]], succProb = [0.5], start = 0, end = 2
输出:0.00000
解释:节点 0 和 节点 2 之间不存在路径
提示:
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
- 每两个节点之间最多有一条边
class Solution {
class Edge {
public int from;
public int to;
public double prob;
public Edge(int from, int to, double prob) {
this.from = from;
this.to = to;
this.prob = prob;
}
}
public double maxProbability(int n, int[][] edges, double[] succProb, int start, int end) {
//在1000个点时会超时
Map<Integer, List<Edge>> graph = new HashMap<>();
double[] maxProb = new double[n];
for(int j=0;j<n;j++) graph.put(j, new LinkedList<>());
for (int i = 0; i < edges.length; i++) {
//有则add
graph.get(edges[i][0]).add(new Edge(edges[i][0], edges[i][1], succProb[i]));
graph.get(edges[i][1]).add(new Edge(edges[i][1], edges[i][0], succProb[i]));
}
if(graph.get(start)==null) return 0;
Queue<Edge> queue = new LinkedList<>();
queue.addAll(graph.get(start));
maxProb[start]=1;
double curProb=1;
while (!queue.isEmpty()) {
Edge edge = queue.poll();
//当前概率=到达前一个点的最大概率*当前路径(边)的概率
curProb = maxProb[edge.from]* edge.prob;;
//若已经到达了终点,比较当前概率是否为最大概率
if(edge.to==end) maxProb[end] = Math.max(curProb,maxProb[end]);
//如果当前点没有边或者当前概率小于从其他路径到达点的概率
if (graph.get(edge.to)==null||curProb < maxProb[edge.to]) {
} else {
//当前概率最大
queue.addAll(graph.get(edge.to));
maxProb[edge.to] = curProb;
}
}
return maxProb[end];
}
}
最开始的思路就是用BFS,用一个double数组maxProb 来维护从start出发到每个点的最大概率,并且在遍历途中不断更新该数组,直到BFS遍历完,返回数组中到达终点的最大概率即可。
但是在1000个点时,算法超时。
之后看了题解,之前的算法需要计算所有的边的概率,导致了时间复杂度过高,实际上可以使用Dijkstra算法了。简单介绍一下 Dijkstra算法是单源最短路算法,不能处理带负边权的情况,一般用邻接矩阵或邻接表存图,其原理基于一个前提:对于一个顶点 A 最短的邻接边 B,从 A 到 B 不可能找到比 AB 更短的路径,因为 AB 已经是最短的了,从其它路径走的话必然经过比 AB 更长的路径。
进阶上可以使用优先队列,减少了自己需要实现找到的最短路径的代码量。
public double maxProbability(int n, int[][] edges, double[] succProb, int start, int end) {
Map<Integer, List<Edge>> graph = new HashMap<>();
double[] maxProb = new double[n];
for (int j = 0; j < n; j++) graph.put(j, new LinkedList<>());
for (int i = 0; i < edges.length; i++) {
graph.get(edges[i][0]).add(new Edge(edges[i][0], edges[i][1], succProb[i]));
graph.get(edges[i][1]).add(new Edge(edges[i][1], edges[i][0], succProb[i]));
}
if (graph.get(start) == null) return 0;
//使用优先队列,因为求的是最大概率所以比较函数改成如下所示,大的在前
PriorityQueue<Edge> queue = new PriorityQueue<>((Edge a, Edge b) -> Double.compare(b.prob, a.prob));
queue.offer(new Edge(start,start,1));
//visited数组,标记是否访问过该点
int[] visited = new int[n];
double curProb;
while (!queue.isEmpty()) {
Edge poll = queue.poll();
//如果已经访问过,跳过
if (visited[poll.to] == 1) continue;
//到达终点返回
if (poll.to == end) return poll.prob;
visited[poll.to] = 1;
if (graph.get(poll.to) == null) {
} else {
//如果没到达终点且当前点存在路径通往其他点
for(Edge edge:graph.get(poll.to)){
//更新概率,prob是从start到edge.to的概率
edge.prob = poll.prob*edge.prob;
//加入优先队列,概率最大的会在队列顶端,时间复杂度(nlogn)
//Priority queue represented as a balanced binary heap
queue.offer(edge);
}
}
}
return 0;
}