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 thei
-th item in the sorted array (to the left of thei
-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/
LeetCode All in One 题目讲解汇总(持续更新中…)
转载请注明来源于 Grandyang 的博客 (grandyang.com),欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 grandyang@qq.com