Sort Items by Groups Respecting Dependencies

hard graph topological sort kahn

Problem

There are n items, each belonging to a group (group -1 means no group). beforeItems[i] lists items that must come before item i. Return a sorted order of items so that items of the same group are contiguous and all beforeItems constraints are satisfied, or an empty array if impossible. The canonical solution runs a topological sort over the groups and a topological sort of items within each group; both layers use Kahn's algorithm and a cycle means no valid order exists.

Inputn = 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]
Items in group 0 (3,4,6) stay together, items in group 1 (2,5) stay together, and every beforeItems edge points earlier in the order.

The visualization below focuses on the core engine: Kahn's topological sort over a dependency graph. Enter directed edges a→b (a must come before b); the algorithm peels off zero in-degree nodes one at a time. If some nodes never reach in-degree 0, a cycle exists.

from collections import deque

def sort_items(n, m, group, before_items):
    # Give every ungrouped item its own group id.
    for i in range(n):
        if group[i] == -1:
            group[i] = m
            m += 1

    def topo(graph, indeg, nodes):
        q = deque([x for x in nodes if indeg[x] == 0])
        order = []
        while q:
            x = q.popleft()
            order.append(x)
            for y in graph[x]:
                indeg[y] -= 1
                if indeg[y] == 0:
                    q.append(y)
        return order if len(order) == len(nodes) else []

    g_graph = [[] for _ in range(m)]
    g_indeg = [0] * m
    i_graph = [[] for _ in range(n)]
    i_indeg = [0] * n
    for i in range(n):
        for j in before_items[i]:
            if group[i] == group[j]:
                i_graph[j].append(i); i_indeg[i] += 1
            else:
                g_graph[group[j]].append(group[i]); g_indeg[group[i]] += 1

    group_order = topo(g_graph, g_indeg, range(m))
    if not group_order:
        return []
    item_order = topo(i_graph, i_indeg, range(n))
    if not item_order:
        return []

    buckets = {gid: [] for gid in group_order}
    for x in item_order:
        buckets[group[x]].append(x)
    res = []
    for gid in group_order:
        res.extend(buckets[gid])
    return res
function sortItems(n, m, group, beforeItems) {
  for (let i = 0; i < n; i++) if (group[i] === -1) group[i] = m++;

  function topo(graph, indeg, nodes) {
    const q = [];
    for (const x of nodes) if (indeg[x] === 0) q.push(x);
    const order = [];
    while (q.length) {
      const x = q.shift();
      order.push(x);
      for (const y of graph[x]) {
        if (--indeg[y] === 0) q.push(y);
      }
    }
    return order.length === nodes.length ? order : [];
  }

  const gGraph = Array.from({ length: m }, () => []);
  const gIndeg = new Array(m).fill(0);
  const iGraph = Array.from({ length: n }, () => []);
  const iIndeg = new Array(n).fill(0);
  for (let i = 0; i < n; i++) {
    for (const j of beforeItems[i]) {
      if (group[i] === group[j]) { iGraph[j].push(i); iIndeg[i]++; }
      else { gGraph[group[j]].push(group[i]); gIndeg[group[i]]++; }
    }
  }

  const gOrder = topo(gGraph, gIndeg, [...Array(m).keys()]);
  if (!gOrder.length) return [];
  const iOrder = topo(iGraph, iIndeg, [...Array(n).keys()]);
  if (!iOrder.length && n) return [];

  const buckets = new Map(gOrder.map((g) => [g, []]));
  for (const x of iOrder) buckets.get(group[x]).push(x);
  const res = [];
  for (const g of gOrder) res.push(...buckets.get(g));
  return res;
}
import java.util.*;

class Solution {
    public int[] sortItems(int n, int m, int[] group, List<List<Integer>> beforeItems) {
        for (int i = 0; i < n; i++) if (group[i] == -1) group[i] = m++;

        List<List<Integer>> gGraph = build(m), iGraph = build(n);
        int[] gIndeg = new int[m], iIndeg = new int[n];
        for (int i = 0; i < n; i++) {
            for (int j : beforeItems.get(i)) {
                if (group[i] == group[j]) { iGraph.get(j).add(i); iIndeg[i]++; }
                else { gGraph.get(group[j]).add(group[i]); gIndeg[group[i]]++; }
            }
        }

        List<Integer> gOrder = topo(gGraph, gIndeg, m);
        List<Integer> iOrder = topo(iGraph, iIndeg, n);
        if (gOrder.isEmpty() || iOrder.isEmpty()) return new int[0];

        Map<Integer, List<Integer>> buckets = new HashMap<>();
        for (int g : gOrder) buckets.put(g, new ArrayList<>());
        for (int x : iOrder) buckets.get(group[x]).add(x);
        int[] res = new int[n]; int k = 0;
        for (int g : gOrder) for (int x : buckets.get(g)) res[k++] = x;
        return res;
    }

    private List<List<Integer>> build(int s) {
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < s; i++) g.add(new ArrayList<>());
        return g;
    }

    private List<Integer> topo(List<List<Integer>> g, int[] indeg, int cnt) {
        Deque<Integer> q = new ArrayDeque<>();
        for (int x = 0; x < cnt; x++) if (indeg[x] == 0) q.add(x);
        List<Integer> order = new ArrayList<>();
        while (!q.isEmpty()) {
            int x = q.poll();
            order.add(x);
            for (int y : g.get(x)) if (--indeg[y] == 0) q.add(y);
        }
        return order.size() == cnt ? order : new ArrayList<>();
    }
}
#include <vector>
#include <queue>
using namespace std;

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<vector<int>> gGraph(m), iGraph(n);
        vector<int> gIndeg(m, 0), iIndeg(n, 0);
        for (int i = 0; i < n; i++) {
            for (int j : beforeItems[i]) {
                if (group[i] == group[j]) { iGraph[j].push_back(i); iIndeg[i]++; }
                else { gGraph[group[j]].push_back(group[i]); gIndeg[group[i]]++; }
            }
        }

        auto topo = [](vector<vector<int>>& g, vector<int> indeg, int cnt) {
            queue<int> q;
            for (int x = 0; x < cnt; x++) if (indeg[x] == 0) q.push(x);
            vector<int> order;
            while (!q.empty()) {
                int x = q.front(); q.pop();
                order.push_back(x);
                for (int y : g[x]) if (--indeg[y] == 0) q.push(y);
            }
            return (int)order.size() == cnt ? order : vector<int>();
        };

        vector<int> gOrder = topo(gGraph, gIndeg, m);
        vector<int> iOrder = topo(iGraph, iIndeg, n);
        if (gOrder.empty() || iOrder.empty()) return {};

        vector<vector<int>> buckets(m);
        for (int x : iOrder) buckets[group[x]].push_back(x);
        vector<int> res;
        for (int g : gOrder) for (int x : buckets[g]) res.push_back(x);
        return res;
    }
};
Time: O(n + e) Space: O(n + e)