likes
comments
collection
share

【拓扑排序】图论拓扑排序运用

作者站长头像
站长
· 阅读数 14

题目描述

这是 LeetCode 上的 851. 喧闹和富有 ,难度为 中等

Tag : 「拓扑排序」

有一组 n 个人作为实验对象,从 0n - 1 编号,其中每个人都有不同数目的钱,以及不同程度的安静值(quietness)。为了方便起见,我们将编号为 x 的人简称为 "person x"

给你一个数组 richer ,其中 richer[i] = [ai, bi] 表示 personpersonperson aia_iai 比 personpersonperson bib_ibi 更有钱。另给你一个整数数组 quiet,其中 quiet[i]personiperson_ipersoni 的安静值。

richer 中所给出的数据 逻辑自恰(也就是说,在 personxperson_xpersonxpersonyperson_ypersony 更有钱的同时,不会出现 personyperson_ypersonypersonxperson_xpersonx 更有钱的情况 )。

现在,返回一个整数数组 answer 作为答案,其中 answer[x] = y 的前提是,在所有拥有的钱肯定不少于 person x 的人中,person y 是最安静的人(也就是安静值 quiet[y] 最小的人)。

示例 1:

输入:richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]], quiet = [3,2,5,4,6,1,7,0]

输出:[5,5,2,5,4,5,6,7]

解释: 
answer[0] = 5,
person 5 比 person 3 有更多的钱,person 3 比 person 1 有更多的钱,person 1 比 person 0 有更多的钱。
唯一较为安静(有较低的安静值 quiet[x])的人是 person 7,
但是目前还不清楚他是否比 person 0 更有钱。
answer[7] = 7,
在所有拥有的钱肯定不少于 person 7 的人中(这可能包括 person 3456 以及 7),
最安静(有较低安静值 quiet[x])的人是 person 7。
其他的答案也可以用类似的推理来解释。

示例 2:

输入:richer = [], quiet = [0]

输出:[0]

提示:

  • n==quiet.lengthn == quiet.lengthn==quiet.length
  • 1<=n<=5001 <= n <= 5001<=n<=500
  • 0<=quiet[i]<n0 <= quiet[i] < n0<=quiet[i]<n
  • quietquietquiet 的所有值 互不相同
  • 0<=richer.length<=n×(n−1)/20 <= richer.length <= n \times (n - 1) / 20<=richer.length<=n×(n1)/2
  • 0<=ai,bi<n0 <= ai, bi < n0<=ai,bi<n
  • ai!=biai != biai!=bi
  • richer 中的所有数对 互不相同
  • richer 的观察在逻辑上是一致的

拓扑排序

根据题意,我们可以使用 richer 进行建图(邻接矩阵/邻接表),对于每组 richer[i]=(ai,bi)richer[i] = (a_i, b_i)richer[i]=(ai,bi) 而言,添加一条从 aaabbb 的有向边(有钱指向没钱)。

其中题目中的「richer 逻辑自恰」是指在该图中不存在环,即为 DAG。

因此我们可以在建图过程中,同时统计每个节点的入度数,然后在图中跑一遍拓扑排序来求得答案 ansansans

对「图论 拓扑排序」不了解的同学,可以先看前置 🧀:拓扑排序入门,里面详细说明了「拓扑排序的基本流程」&「反向图 + 拓扑排序做法的正确性证明」。

起始时,每个 ans[i]=ians[i] = ians[i]=i,然后将统计入度为 000 的节点进行入队,每次出队时,将该节点删掉,对该 DAG 带来影响是「该节点的邻点的入度减一」,若更新入度后数值为 000,则将该邻点进行入队操作。

同时,利用跑拓扑排序过程中的 t−>ut -> ut>u 关系,尝试使用 ans[t]ans[t]ans[t] 更新 ans[u]ans[u]ans[u](由于存在 ttt 指向 uuu 的边,说明 tttuuu 有钱,此时检查两者的安静值,若满足 quiet[ans[t]]<quiet[ans[u]]quiet[ans[t]] < quiet[ans[u]]quiet[ans[t]]<quiet[ans[u]],则用 ans[t]ans[t]ans[t] 更新 ans[u]ans[u]ans[u])。

本题为稠密图(点数为 nnn,边数为 mmm,当 mmmn2n^2n2 为同一数据级,定义以为稠密图),可直接使用「邻接矩阵」进行存图。 关于何种图选择什么存图方式,在 这里 详细讲过,本次不再赘述。

代码(P1P1P1 为邻接矩阵,P2P2P2 为邻接表):

class Solution {
    public int[] loudAndRich(int[][] richer, int[] quiet) {
        int n = quiet.length;
        int[][] w = new int[n][n];
        int[] in = new int[n];
        for (int[] r : richer) {
            int a = r[0], b = r[1];
            w[a][b] = 1; in[b]++;
        }
        Deque<Integer> d = new ArrayDeque<>();
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = i;
            if (in[i] == 0) d.addLast(i);
        }
        while (!d.isEmpty()) {
            int t = d.pollFirst();
            for (int u = 0; u < n; u++) {
                if (w[t][u] == 1) {
                    if (quiet[ans[t]] < quiet[ans[u]]) ans[u] = ans[t];
                    if (--in[u] == 0) d.addLast(u);
                }
            }
        }
        return ans;
    }
}
class Solution {
    int N = 510, M = N * N + 10;
    int[] he = new int[N], e = new int[M], ne = new int[M];
    int idx;
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx;
        idx++;
    }
    public int[] loudAndRich(int[][] richer, int[] quiet) {
        int n = quiet.length;
        int[] in = new int[n];
        Arrays.fill(he, -1);
        for (int[] r : richer) {
            int a = r[0], b = r[1];
            add(a, b); in[b]++;
        }
        Deque<Integer> d = new ArrayDeque<>();
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = i;
            if (in[i] == 0) d.addLast(i);
        }
        while (!d.isEmpty()) {
            int t = d.pollFirst();
            for (int i = he[t]; i != -1; i = ne[i]) {
                int u = e[i];
                if (quiet[ans[t]] < quiet[ans[u]]) ans[u] = ans[t];
                if (--in[u] == 0) d.addLast(u);
            }
        }
        return ans;
    }
}
  • 时间复杂度:令 nnnperson 数量(点数),mmmricher 长度(边数)。的建图的复杂度为 O(m)O(m)O(m);拓扑排序复杂度为 O(m+n)O(m + n)O(m+n)。整体复杂度为 O(m+n)O(m + n)O(m+n)
  • 空间复杂度:O(m+n)O(m + n)O(m+n)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.851 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉