AlgoMaster Logo

Sort Items by Groups Respecting Dependencies

Last Updated: March 28, 2026

hard
5 min read

Understanding the Problem

This problem asks us to sort items while satisfying two constraints at the same time. First, there are explicit ordering dependencies: some items must appear before others, as given by beforeItems. Second, there is a grouping constraint: all items belonging to the same group must be adjacent in the final ordering.

Think of it like scheduling tasks for different teams in a company. Each task may depend on other tasks being completed first, and you want each team's tasks grouped together in the schedule so the team can work in a focused block. You need a single ordering that respects both the dependencies and the grouping requirement.

The key observation is that this is really two topological sort problems stacked on top of each other. We need to figure out (1) what order do the group blocks appear in, and (2) within each group block, what order do the items appear in. If any item A in group X must come before item B in group Y, that also tells us group X's block must appear before group Y's block. Both levels of ordering can be computed with topological sort, and if either level has a cycle, no valid answer exists.

Key Constraints:

  • 1 <= m <= n <= 3 * 10^4 -> With up to 30,000 items, we need an approach that runs in roughly O(n + E) time, where E is the total number of dependency edges. Anything quadratic is risky.
  • 0 <= beforeItems[i].length <= n - 1 -> Each item can depend on up to n-1 others. The total number of edges across all items could be O(n^2) in the worst case, though typical inputs are much sparser.
  • group[i] == -1 means the item belongs to no group -> These ungrouped items need special handling. Since they do not need to be adjacent to anything, they can be placed freely as long as dependencies are satisfied.

Approach 1: Single-Level Topological Sort (Naive)

Intuition

The most natural first attempt is to ignore the grouping constraint entirely and just do a regular topological sort on all items using the beforeItems dependencies. Topological sort gives us a valid ordering where every dependency is respected, so that part is handled.

The problem? This ordering has no reason to keep items from the same group together. For instance, if items 2 and 5 are in group 1, the topological sort might produce something like [..., 2, ..., 3, 4, ..., 5, ...] with items from other groups sandwiched between 2 and 5. That violates the grouping constraint.

You might think: "Okay, after the topological sort, just rearrange items so that group members are adjacent." But rearranging can break the dependency ordering. If item 3 (group 0) must come before item 5 (group 1), and item 2 (group 1) must come after some item that comes after 3, moving 2 next to 5 might place 2 before 3, violating a dependency.

So a single-level topological sort is fundamentally insufficient. We need to think about this differently.

Algorithm

  1. Build a directed graph from beforeItems dependencies.
  2. Run topological sort (BFS or DFS) on all n items.
  3. If a cycle is detected, return an empty list.
  4. Try to rearrange the result so group members are adjacent.
  5. If rearrangement breaks any dependency, return an empty list.

This approach cannot be implemented cleanly because step 4 (rearranging while preserving dependencies) is essentially solving the original problem all over again.

A single topological sort has no mechanism to enforce grouping. It sorts items by dependencies alone, and there is no way to inject the "keep groups together" constraint into a standard topological sort. What if we sorted at two levels: first determining the order of groups relative to each other, then the order of items within each group?

Approach 2: Two-Level Topological Sort (Kahn's BFS)

Intuition

The breakthrough insight is to decompose this into two separate topological sort problems that work together harmoniously.

Picture the final output as a sequence of "group blocks," where each block contains all items from one group in some order. We need to decide two things: (1) what order do the group blocks appear in, and (2) within each block, what order do the items appear in.

Both of these orderings are constrained by the beforeItems dependencies. If item A (in group X) must come before item B (in group Y, where X != Y), then group X's block must come before group Y's block entirely. If A and B are both in the same group, then A must come before B within that group's block.

So we build two graphs: a group-level graph where edges represent "group X must come before group Y" relationships derived from inter-group item dependencies, and an item-level graph where edges represent the original beforeItems dependencies (used for within-group sorting).

For ungrouped items (where group[i] == -1), we assign each one its own unique "virtual group" of size 1. A group with one item trivially satisfies the adjacency requirement. This eliminates the special case entirely.

Algorithm

  1. Assign unique group IDs to ungrouped items. For each item where group[i] == -1, assign group[i] = nextGroupId++ starting from m. Track the total number of groups.
  2. Build two graphs: For each item i and each predecessor p in beforeItems[i], add edge p -> i to the item graph. If group[p] != group[i], also add edge group[p] -> group[i] to the group graph (with deduplication).
  3. Map items to groups. Create a list for each group containing its member items.
  4. Topological sort on the group graph. Use Kahn's BFS. If not all groups are processed, a cycle exists. Return an empty list.
  5. Topological sort within each group. For each group in the order from step 4, compute local in-degrees (counting only predecessors within the same group), run BFS, and append sorted items to the result. If not all members are processed, return an empty list.
  6. Return the concatenated result.

Example Walkthrough

1Assign virtual groups: item 0→G2, item 1→G3, item 7→G4. Build group graph: edge G0→G3
G0 (3,4,6)G3 (1)G1 (2,5)G2 (0)G4 (7)
1/7

Code