21. Calculate employee levels, balanced employees, and a level histogram from an org chart.
Given an organization chart represented as a tree, calculate each employee's level, identify employees with equal numbers of nodes above and below, and produce a histogram by level.
I would use one depth-first search starting from the CEO at level 0. When I enter an employee, I record the current depth as the level and increase that level’s histogram count. When the recursive calls return, I calculate the employee’s subtree size. The number below is subtree size minus one. If it equals the depth, the employee is balanced. Every employee is visited once, so the time complexity is O(n), and the auxiliary space complexity is O(n).
See the Code while reading this explanation.
The input is a rooted organization tree. We need to calculate each employee’s level, find employees whose number of managers above equals their number of descendants below, and count how many employees appear at each level. A depth-first search fits this problem because depth is available while moving down the tree, while subtree size is available when the recursive calls return.
- What input sizes, value ranges, and edge cases should the solution handle?
- What output should be returned for empty, invalid, or duplicate input?
- Should I prioritize execution time or memory use, and may I use the standard library?
The organization chart is stored as an adjacency list. Each employee maps to a list of direct reports.
For the example:
CEO -> [CTO, CFO, COO] CTO -> [Dev1] CFO -> [Fin1] COO -> [Ops1] Dev1 -> [] Fin1 -> [] Ops1 -> []
The CEO is the root.
The level of an employee is the number of managers above that employee. The CEO has level
- CTO, CFO, and COO have level
- Dev1, Fin1, and Ops1 have level 2.
The number below an employee means the number of descendants in that employee’s subtree. If subtree_size includes the employee, then descendants equals subtree_size minus one.
An employee is balanced when:
depth == subtree_size - 1
The function returns a map of employee levels, a list of balanced employees, and a histogram that maps each level to its employee count.
I call DFS with the CEO and depth 0.
When DFS enters an employee, it records the employee’s level and increases the histogram count for that level.
It then visits every direct report with depth plus one.
When all child calls return, DFS adds their subtree sizes and includes the current employee. This gives the exact subtree size for the current employee.
The levels map starts as an empty dictionary. It stores employee name to level.
The histogram starts empty. It stores level to employee count.
The balanced list starts empty. It stores employees that pass the balance condition.
The traversal starts at CEO with depth 0.
The main invariant is: after DFS returns from an employee, the returned subtree size is correct for that employee, and every employee in that subtree has already been processed.
Step 1: Enter CEO at depth 0. Record CEO: 0. The histogram becomes {0: 1}.
Step 2: Enter CTO at depth 1. Record CTO: 1. The histogram becomes {0: 1, 1: 1}.
Step 3: Enter Dev1 at depth 2. Record Dev1: 2. The histogram becomes {0: 1, 1: 1, 2: 1}. Dev1 has no reports, so its subtree size is 1. Its descendant count is 0. Depth 2 does not equal 0, so Dev1 is not balanced.
Step 4: Return to CTO. CTO’s subtree contains CTO and Dev1, so its subtree size is 2. Its descendant count is 1. Its depth is also 1, so CTO is balanced.
Step 5: Enter CFO at depth 1. Record CFO: 1. The histogram becomes {0: 1, 1: 2, 2: 1}.
Step 6: Enter Fin1 at depth 2. Record Fin1: 2. The histogram becomes {0: 1, 1: 2, 2: 2}. Fin1 has subtree size 1 and 0 descendants. Depth 2 does not equal 0, so Fin1 is not balanced.
Step 7: Return to CFO. CFO has subtree size 2 and 1 descendant. Its depth is 1, so CFO is balanced.
Step 8: Enter COO at depth 1. Record COO: 1. The histogram becomes {0: 1, 1: 3, 2: 2}.
Step 9: Enter Ops1 at depth 2. Record Ops1: 2. The histogram becomes {0: 1, 1: 3, 2: 3}. Ops1 has subtree size 1 and 0 descendants. Depth 2 does not equal 0, so Ops1 is not balanced.
Step 10: Return to COO. COO has subtree size 2 and 1 descendant. Its depth is 1, so COO is balanced.
Step 11: Return to CEO. The CEO’s subtree contains all 7 employees. The CEO therefore has 6 descendants. Its depth is 0, so the CEO is not balanced.
The final result is:
levels = {CEO: 0, CTO: 1, CFO: 1, COO: 1, Dev1: 2, Fin1: 2, Ops1: 2}
balanced = [CTO, CFO, COO]
histogram = {0: 1, 1: 3, 2: 3}
The depth passed into DFS is exactly the number of managers above the current employee.
The subtree size returned by DFS includes the current employee and every descendant. Subtracting one removes the current employee and gives the exact number of descendants below.
Therefore, depth == subtree_size - 1 correctly identifies balanced employees.
The histogram is correct because every employee increases exactly one bucket for the employee’s level.
The outer function creates the levels dictionary, histogram, and balanced list.
The nested dfs function receives an employee and that employee’s depth. It records the level and histogram count before visiting direct reports.
It starts subtree_size at 1 because the subtree contains the current employee. Each recursive child call returns a child subtree size, which is added to the total.
After all children return, the code calculates descendants as subtree_size - 1. It adds the employee to balanced when descendants equals depth. It then returns the subtree size to the parent.
After DFS finishes, the code returns the levels map, balanced list, and histogram sorted by level.
Let n be the number of employees. Each employee is entered once and returned from once. Therefore, the time complexity is O(n).
The auxiliary space complexity is O(n). The levels map, histogram, balanced list, and recursion stack can grow with the number of employees.
Relevant edge cases are a single-employee tree, a skewed tree, employees with no reports, and employees represented by empty child lists. A single CEO has depth 0 and 0 descendants, so that CEO is balanced under the same rule.
The key insight is that the two values used by the balance test become available at different parts of one DFS. The employee’s depth is known when DFS enters the node, so it gives the number of managers above. The employee’s subtree size is known after all child calls return, so subtree_size - 1 gives the number of descendants below. The invariant is that when DFS returns from a node, its complete subtree has been processed and its returned subtree size is correct. This lets one traversal produce all three required outputs.
from collections import defaultdict
from typing import Dict, List, Tuple
def analyze_org_chart(
org: Dict[str, List[str]], root: str
) -> Tuple[Dict[str, int], List[str], Dict[int, int]]:
"""Return employee levels, balanced employees, and a level histogram."""
# employee name -> level from the root
levels: Dict[str, int] = {}
# level -> number of employees at that level
histogram = defaultdict(int)
# Employees whose managers above equal descendants below
balanced: List[str] = []
def dfs(employee: str, depth: int) -> int:
# Record the employee's level when entering the node.
levels[employee] = depth
# Count this employee in the correct level bucket.
histogram[depth] += 1
# The subtree contains at least the current employee.
subtree_size = 1
# Visit each direct report with the next depth.
for report in org.get(employee, []):
subtree_size += dfs(report, depth + 1)
# Remove the current employee to count only descendants.
descendants = subtree_size - 1
# depth is the number of managers above this employee.
if depth == descendants:
balanced.append(employee)
# Give the complete subtree size to the parent call.
return subtree_size
# The root starts at level 0.
dfs(root, 0)
# Return the histogram in increasing level order.
return levels, balanced, dict(sorted(histogram.items()))
if __name__ == "__main__":
org_chart = {
"CEO": ["CTO", "CFO", "COO"],
"CTO": ["Dev1"],
"CFO": ["Fin1"],
"COO": ["Ops1"],
"Dev1": [],
"Fin1": [],
"Ops1": [],
}
levels, balanced, histogram = analyze_org_chart(org_chart, "CEO")
print("levels =", levels)
print("balanced =", balanced)
print("histogram =", histogram)Let n be the number of employees. The time complexity is O(n) because DFS visits each employee once and processes each reporting relationship once. The auxiliary space complexity is O(n). The levels dictionary can hold n entries. The balanced list can contain up to n employees. The histogram can contain up to n level entries. The recursion stack can also grow to n when the organization tree is completely skewed.
This pattern is useful for organization charts, file-system trees, category trees, reporting hierarchies, and other rooted trees. It is helpful when a program needs information from above a node, such as depth, and information from below a node, such as subtree size or descendant count, during the same traversal.
This problem tests whether a candidate can combine top-down and bottom-up information in a tree. The level comes from the path from the root, while the descendant count comes from recursive return values. The interviewer is also checking recursion, adjacency-list traversal, invariant reasoning, management of several result structures, correct execution order, and accurate time and space analysis.
A common mistake is counting only direct reports instead of all descendants. Another mistake is comparing depth with subtree size without subtracting the current employee. Some candidates check the balance condition before all child calls return, when the final subtree size is still unknown. Others forget to update the histogram on every node entry. It is also incorrect to claim O(1) auxiliary space because the result structures and recursion stack can grow with the tree.
Explain the solution with one sentence before coding: depth gives the number above, and subtree size minus one gives the number below. Then show that DFS provides both values in one traversal.









