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/