Print an Org Chart as a Depth-Ordered List from Supervisor Pairs
SnowflakeSoftware Engineer · OnsiteLast updated
Given a flat list of (person, supervisor) ID pairs, reconstruct the organization as a forest rooted at whoever has no supervisor, then emit a deterministic preorder listing of (id, depth). Tests root detection, building ordered adjacency lists, and careful DFS ordering.
You're given a flat list of supervisor links for a company's staff directory. Each entry is a pair (person_id, supervisor_id), both positive integers, meaning person_id reports directly to supervisor_id. Someone who never shows up as a person_id in any pair has no supervisor and sits at the top of their own chain. Several people can each head an independent chain, so the whole structure is a forest, not a single tree.
Produce a flattened, depth-ordered view of the whole staff directory suitable for rendering as an indented chart.
def org_forest_preorder(links: list[tuple[int, int]]) -> list[tuple[int, int]]:
"""Return one (id, depth) pair per distinct person, in display order."""
Rules
- A person at the top of a chain (never appears as
person_id) has depth0. Everyone else has one more than their direct supervisor's depth. - Every distinct ID that appears anywhere in
linksmust appear exactly once in the output. - Output order is a preorder walk: sort the chain tops by ID and visit them lowest first; within a chain, after listing a person, recurse fully into one direct report's subtree before moving on to the next, picking which direct report goes first by comparing their IDs lowest to highest.
- Each result entry is a
(id, depth)tuple.
Constraints
1 <= len(links) <= 10**5, IDs up to10**9.- Each person appears as
person_idin at most one pair (a single direct supervisor). - No cycles: tracing supervisors upward from any starting person is guaranteed to terminate at a chain top.
- Nothing caps how many generations one chain may have short of the total number of links supplied.
Example
Input: links = [(30, 10), (40, 10), (50, 30), (60, 30), (70, 50), (90, 80)]
Output: [(10, 0), (30, 1), (50, 2), (70, 3), (60, 2), (40, 1), (80, 0), (90, 1)]
10 and 80 never appear as a person_id, so they're the two chain tops, visited in ascending order: 10 before 80. Under 10, direct reports 30 and 40 are visited in that order; 30's whole subtree (30, then 50, then 50's child 70, then 50's sibling 60) comes entirely before 40. The chain under 80 is just 80 then 90.
A second check: links = [(200, 100), (300, 200), (400, 200), (500, 300)] should produce [(100, 0), (200, 1), (300, 2), (500, 3), (400, 2)] — a single four-generation chain with one extra branch at generation 2.
Hints
Hint 1
First pass: collect every ID that appears, and every ID that appears as a person_id (has a supervisor). The difference between those two sets is your root list.
Hint 2
Build a supervisor-to-direct-reports map, sorting each supervisor's report list once up front so you never have to sort during the walk.
Hint 3
The output order is exactly what a recursive DFS naturally produces if you visit roots in ascending order and each node's children in ascending order.
Hint 4
Depth is just 'how many visit() calls deep' — pass it down as a parameter rather than computing it separately.
Follow-up questions
- How would you answer 'what is person X's depth' or 'list person X's full management chain to the top' without rebuilding the whole forest each time?
- How would the algorithm need to change if a person could report to two different supervisors (a DAG instead of a forest)?
- Given 10^5 links with a chain depth near 10^5, how would you avoid a Python recursion-limit crash?
- How would you incrementally update the output if one supervisor link changes, without recomputing the whole forest?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.