zoukankan      html  css  js  c++  java
  • [LeetCode] 1203. Sort Items by Groups Respecting Dependencies 项目管理


    There are n items each belonging to zero or one of m groups where group[i] is the group that the i-th item belongs to and it's equal to -1 if the i-th item belongs to no group. The items and the groups are zero indexed. A group can have no item belonging to it.

    Return a sorted list of the items such that:

    • The items that belong to the same group are next to each other in the sorted list.
    • There are some relations between these items where beforeItems[i] is a list containing all the items that should come before the i-th item in the sorted array (to the left of the i-th item).

    Return any solution if there is more than one solution and return an empty list if there is no solution.

    Example 1:

    Input: n = 8, m = 2, group = [-1,-1,1,0,0,1,0,-1], beforeItems = [[],[6],[5],[6],[3,6],[],[],[]]
    Output: [6,3,4,1,5,2,0,7]
    

    Example 2:

    Input: n = 8, m = 2, group = [-1,-1,1,0,0,1,0,-1], beforeItems = [[],[6],[5],[6],[3],[],[4],[]]
    Output: []
    Explanation: This is the same as example 1 except that 4 needs to be before 6 in the sorted list.
    

    Constraints:

    • 1 <= m <= n <= 3 * 104
    • group.length == beforeItems.length == n
    • -1 <= group[i] <= m - 1
    • 0 <= beforeItems[i].length <= n - 1
    • 0 <= beforeItems[i][j] <= n - 1
    • i != beforeItems[i][j]
    • beforeItems[i] does not contain duplicates elements.

    这道题给了n个结点,说是其属于m个组,即群组序号在 [0, m-1] 之间,但是又说了还存在一些结点不属于任何的群组,其群组号为 -1,这个需要稍后做些特殊的处理。题目同时还说了可能有的群组中没有结点,现在让给所有的结点进行排序,需要满足一些特定的条件:属于同一个群组的结点必须排在一起,这里还有一个 beforeItems 数组,表示 beforeItems[i] 中的所有结点必须排在结点i的前面,若无法满足这些条件,则返回空数组。这是一道有向图的排序问题,一般都是要使用拓扑排序 Topological Sort 来做,这里比较麻烦的是存在不同的群组,之间可能并不连通,可能需要多次的拓扑排序。不过这里的 beforeItems 数组倒是有可能把不同群组的结点连接起来,这里进行两次拓扑排序,分别是针对群组层面和结点层面。既然要针对群组层面,就要先处理一下那些不属于任何群组的结点,因为其群组号为 -1,不方便处理,这里可以从m开始,分别赋一个不同的值,这样所有的群组号就是正数了。然后就可以新建图的结构了,这里还是用邻接链表的形式来存有向图,用 graphGroup 和 graphItem 分别表示群组和结点的图结构,同时用两个对应的入度数组 inGroup 和 inItem。接下来就是建立图结构了,遍历每个结点i,先找出其属于哪个群组,这里用 to_group 表示,然后就是遍历其对应的 beforeItems 中的结点j,因为这些结点必须在结点i之前,所以它们都应该连到i。对于每个结点j,找出其属于哪个群组,用 from_group 来表示,若此时i和j属于不同的两个群组,且之前二者没有建立联系,则此时将二者的群组连接起来,并且对应入度 inGroup 自增1,这是更新了群组的图结构。接下来还要更新结点的图结构,若二个结点没有相连,则将j连到i,并且i的入度自增1。

    当群组图和结点图都建立好了之后,就可以进行拓扑排序了,将排序的过程放到一个子函数中,这样就可以复用代码了。先来看如何进行拓扑排序,可以用 BFS 或者 DFS 来遍历有向图。这里用 BFS 来做,借助队列 queue 来遍历,遍历入度数组,将所有入度为0的结点都放入 queue 中。进行 while 循环,取出队首元素t,将其加入结果 res 中,然后遍历和其所有相连的结点 next,将其对应的入度值减1,若减到0了,则将该结点加入 queue 中。遍历完成了之后,再次检查入度数组,若此时还有大于0的入度值,则表示可能出现环,返回空数组,否则就返回结果 res 即可。在分别得到了群组和结点的有序排列 group_sorted 和 item_sorted 之后,先进行判断,若有任意一个为空,则返回空数组。接下来需要将排序后的结点分组进行保存,用一个二维数组 group2item,遍历所有有序的 item,将其放到其对应的群组内。等结点按群组归纳好了之后,就可以排最终的结点顺序了,需要根据排好的群组顺序进行排列,按群组顺序取出其中的所有结点加入结果 res 即可,参见代码如下:


    解法一:

    class Solution {
    public:
        vector<int> sortItems(int n, int m, vector<int>& group, vector<vector<int>>& beforeItems) {
            for (int i = 0; i < n; ++i) {
                if (group[i] == -1) group[i] = m++;
            }
            vector<unordered_set<int>> graphGroup(m), graphItem(n);
            vector<int> inGroup(m), inItem(n), res;
            for (int i = 0; i < n; ++i) {
                int to_group = group[i];
                for (int j : beforeItems[i]) {
                    int from_group = group[j];
                    if (from_group != to_group && !graphGroup[from_group].count(to_group)) {
                        graphGroup[from_group].insert(to_group);
                        ++inGroup[to_group];
                    }
                    if (!graphItem[j].count(i)) {
                        graphItem[j].insert(i);
                        ++inItem[i];
                    }
                }
            }
            vector<int> group_sorted = helper(graphGroup, inGroup), item_sorted = helper(graphItem, inItem);
            if (group_sorted.empty() || item_sorted.empty()) return {};
            vector<vector<int>> group2item(m);
            for (int item : item_sorted) {
                group2item[group[item]].push_back(item);
            }
            for (int group_id : group_sorted) {
                for (int item : group2item[group_id]) {
                    res.push_back(item);
                }
            }
            return res;
        }
        vector<int> helper(vector<unordered_set<int>>& g, vector<int>& inDegree) {
            vector<int> res;
            queue<int> q;
            for (int i = 0; i < inDegree.size(); ++i) {
                if (inDegree[i] == 0) q.push(i);
            }
            while (!q.empty()) {
                int t = q.front(); q.pop();
                res.push_back(t);
                for (int next : g[t]) {
                    if (--inDegree[next] == 0) {
                        q.push(next);
                    } 
                }
            }
            for (int i = 0; i < inDegree.size(); ++i) {
                if (inDegree[i] > 0) return {};
            }
            return res;
        }
    };
    

    我们再来看一种只需要一个图结构的方法,主要参考了 votrubac 大神的帖子,这里需要用一些辅助结点,具体来说,是在所有群组中都加了一个公共的起始结点,和终止结点,所谓的起始结点,就是该结点通向群组内所有的结点,同理,群组内所有的结点都通向终止结点,这样做的好处是可以有一个公用的入度为0的起始结点开始每个群组的拓扑排序,则群组之间不会互相干扰。由于每个群组都要新增两个辅助结点,则总共的结点数就变成了 n + 2*m,这样就只需要一个结点的图结构就行了。这里还是用邻接链表的形式来保存图结构,由于没有查找需求,直接使用一个二维数组即可。每个群组的公用起始结点标号为 n + group[i],终止标号为 n + m + group[i],这样就互不冲突了。遍历所有的结点,若该结点的群组号不是 -1,则将起始结点连到该结点i,且将该结点i连到终止结点。然后再遍历 beforeItems[i] 中的结点j,若结点i和j属于同一个群组,且均不是 -1,则将结点j连到结点i。否则计算结点i的群组号,若其属于 -1,则用i,否则用其起始结点 n + group[i],同理,对于结点j进行相同的操作,然后把结点j所在的群组连到结点i所在的群组。

    这样图结构就建立好了,可以进行拓扑排序了,这里从 n + 2*m - 1 结点开始往前遍历,因为后面的结点值都是新增的辅助结点,都是群组的起始结点或者终止结点,从这些结点开始拓扑排序,就可以完整保持每个群组内部的结点顺序,同时群组之间的顺序也可以保持。为了和上面的解法区分开来,这里使用 DFS 来遍历。这里用一个状态数组 state,大小为 n + 2*m,有三种状态,0表示还未遍历,1表示开始了遍历,但此时还还在继续遍历和其相连的结点中,2表示已经完成了当前结点的遍历。在递归函数中,首先判断当前结点的状态,若不为0,则继续判断,若为2,则返回 true,否则返回 false(此时表示有环存在,无法拓扑排序)。否则将当前结点状态改为1,遍历所有和其相连的结点 next,并对其调用递归函数,若有任意一个结点返回 false 了,则直接返回 false。结束之后,将当前结点状态改为2,并将结点加入结果 res,返回 true 即可。所有的拓扑排序结束后,此时的顺序是和要求的顺序是相反的,因为在递归中是直接将结点加到 res 末尾了,而不是开头。不过没关系,这里直接整个翻转一下就行了,最后别忘了去掉所有的辅助结点,参见代码如下:


    解法二:

    class Solution {
    public:
        vector<int> sortItems(int n, int m, vector<int>& group, vector<vector<int>>& beforeItems) {
            vector<int> t, res(n), state(n + 2 * m);
            vector<vector<int>> g(n + 2 * m);
            for (int i = 0; i < n; ++i) {
                if (group[i] != -1) {
                    g[n + group[i]].push_back(i);
                    g[i].push_back(n + m + group[i]);
                }
                for (int j : beforeItems[i]) {
                    if (group[i] != -1 && group[i] == group[j]) {
                        g[j].push_back(i);
                    } else {
                        int p = group[i] == -1 ? i : n + group[i];
                        int q = group[j] == -1 ? j : n + m + group[j];
                        g[q].push_back(p);
                    }
                }
            }
            for (int i = (int)g.size() - 1; i >= 0; --i) {
                if (!helper(g, i, state, t)) return {};
            }
            reverse(t.begin(), t.end());
            copy_if(t.begin(), t.end(), res.begin(), [&](int i) {return i < n;});
            return res;
        }
        bool helper(vector<vector<int>>& g, int i, vector<int>& state, vector<int>& res) {
            if (state[i] != 0) return state[i] == 2;
            state[i] = 1;
            for (int next : g[i]) {
                if (!helper(g, next, state, res)) return false;
            }
            state[i] = 2;
            res.push_back(i);
            return true;
        }
    };
    

    Github 同步地址:

    https://github.com/grandyang/leetcode/issues/1203


    参考资料:

    https://leetcode.com/problems/sort-items-by-groups-respecting-dependencies/

    https://leetcode.com/problems/sort-items-by-groups-respecting-dependencies/discuss/402945/C%2B%2B-with-picture-generic-topological-sort

    https://leetcode.com/problems/sort-items-by-groups-respecting-dependencies/discuss/1106701/C%2B%2B-Two-level-topological-sort.-Peel-off-the-tricky-parts-then-do-normal-TopoSort.


    LeetCode All in One 题目讲解汇总(持续更新中...)

  • 相关阅读:
    二、VueRouter ---kkb
    一、Vue组件化 ---kkb
    React项目的一些配置以及插件
    四、React全家桶(二)
    三、React全家桶(一)
    二、React组件化
    扩展欧几里得算法(含严谨证明)
    bzoj4034 树上操作
    欧几里得算法(含严谨证明)
    noip2013 车站分级
  • 原文地址:https://www.cnblogs.com/grandyang/p/15187461.html
Copyright © 2011-2022 走看看