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?