Converting Org Tree to Engineer-Only Graph
🌳 Problem Statement
You are given a tree representing a company's organizational structure. The root is always an Engineer.
Each node (Employee) has:
id(Integer)isEngineer(Boolean)reportees(List of Employees)
The Task: Convert this graph into a new graph that contains only Engineers.
- The hierarchy must be preserved as much as possible.
- If an Engineer reports to a Non-Engineer, they should "move up" to report to the nearest Engineer ancestor.
- Constraint: You should create a New Graph (Deep Copy), leaving the original structure untouched.
📝 Example Scenarios
Scenario 1: The "Bridge" Case (Non-Engineer Middleman)
A Non-Engineer sits between two Engineers. The bottom Engineer must move up.
Input Tree:
Engineer(1) -> NonEngineer(2) -> Engineer(3)
Output Tree:
Engineer(1) -> Engineer(3)
- Logic:
NonEngineer(2)is removed.Engineer(3)is promoted to report directly toEngineer(1).
Scenario 2: The "Split" Case (Multiple Orphans)
A Non-Engineer has multiple Engineer reports. All of them must move up together.
Input Tree:
Eng(1) -> NonEng(2) -> [Eng(3), Eng(4)]
Output Tree:
Eng(1) -> [Eng(3), Eng(4)]
- Logic: When
NonEng(2)is removed, bothEng(3)andEng(4)become direct children ofEng(1).
1. The Core Logic: Recursive Filtering
This problem requires a Post-Order Traversal (Bottom-Up) approach. We must process the children before we can decide what the current node looks like.
📉 The Return Type Nuance
Standard tree traversals return a single Node. However, because a Non-Engineer "dissolves" and might leave behind multiple children, our recursive function must return a List of Nodes (or vector<Employee*>).
🧠 The Decision Matrix
For every node current:
- Recurse: First, call the function on all children to get their "sanitized" lists of engineers. Flatten these results into a single list of
valid_children. -
Evaluate Self:
-
If
currentis Engineer:- Create a new copy of
current. - Attach
valid_childrento this new node. -
Return list
[new_current].- If
currentis Non-Engineer:
- If
-
Do not create a node.
- Dissolve self and pass
valid_childrenup to the parent. - Return
valid_children(the list of orphans).
- Create a new copy of
-
2. Example Walkthrough
Let's trace the logic using the specific example provided.
🖼️ Visual Example
Input Tree:
Output Tree (Engineer Only):
📊 Trace Table
| Node | Type | Step 1: Process Children | Step 2: Decision | Return Value |
|---|---|---|---|---|
| E4 | Eng | Leaf (No children) | Create New E4. |
[New_E4] |
| NE2 | Non-Eng | Leaf (No children) | Dissolve. Return empty list. | [] |
| E2 | Eng | Receives [New_E4], []. |
Create New E2. Attach [New_E4]. |
[New_E2] |
| E5 | Eng | Leaf (No children) | Create New E5. |
[New_E5] |
| NE1 | Non-Eng | Receives [New_E5]. |
Dissolve. Pass [New_E5] up. |
[New_E5] |
| E3 | Eng | Leaf (No children) | Create New E3. |
[New_E3] |
| E1 | Eng | Receives: [New_E2], [New_E5], [New_E3] |
Create New E1. Attach all 3 lists. |
[New_E1] |
🏁 Final Structure
New_E1 now has children: [New_E2, New_E5, New_E3]. New_E2 has child [New_E4].
3. Extension: In-Place vs. Deep Copy
Deep Copy (Selected Approach)
- Pros: Thread-safe, cleaner (functional style), original data remains valid for other uses (e.g., HR payroll needs Non-Engineers).
- Cons: \(O(N)\) extra space.
In-Place Modification
- Logic: Pointer manipulation. If
childis Non-Engineer, movechild->reporteesintocurrent->reportees, thendelete child. - Pros: \(O(1)\) extra space (excluding stack).
- Cons: Destructive. Complex iterator invalidation issues in C++ (modifying a vector while iterating it).
4. Practice & Edge Cases
🧪 Common Scenarios
- The "Deep" Non-Engineer Chain
- Scenario:
E1 -> NE1 -> NE2 -> NE3 -> E5 - Logic: The recursion bubbles
E5up through all layers.NE3returns[E5],NE2returns[E5],NE1returns[E5]. FinallyE1adoptsE5.
- Scenario:
- The "Leaf" Non-Engineer
- Scenario:
E1 -> NE1(where NE1 has no children). - Logic:
NE1returns an empty list[].E1adopts nothing. Result:E1with no children.
- Scenario:
- Null Root
- Logic: Return
nullptrimmediately.
- Logic: Return
🛠️ Interview Toolkit: Key Checks
Here is a cheat-sheet for "Strong Hire" signals during the interview.
- Memory Ownership: Since we are using C++
new, explicitly mention who owns these pointers. (Ideally, useunique_ptrin production, but raw pointers are acceptable for whiteboard speed if acknowledged). - Complexity:
- Time: \(O(N)\) (We visit every node once).
- Space: \(O(N)\) (Recursion stack + New nodes).
- Return Type Justification: Be ready to explain why you return a
vectorand not a singleEmployee*. (Answer: "Because a single node removal can result in multiple sub-trees needing re-parenting.")
💻 Appendix: Solution Code
C++ Solution (Deep Copy)
Click to expand solution code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Employee {
int id;
bool isEngineer;
vector<Employee*> reportees;
Employee(int id, bool isEngineer) : id(id), isEngineer(isEngineer) {}
};
class OrgChartConverter {
public:
// Main API
Employee* convertToEngineerGraph(Employee* root) {
if (!root) return nullptr;
// The root is guaranteed to be an Engineer, so result[0] is safe
vector<Employee*> result = helper(root);
return result.empty() ? nullptr : result[0];
}
private:
// Helper function: Returns a LIST of nodes because of the "dissolve" logic
vector<Employee*> helper(Employee* current) {
vector<Employee*> engineersList;
if (!current) return engineersList;
// 1. Recursively process all children first (Bottom-Up)
vector<Employee*> processedSubtree;
for (Employee* child : current->reportees) {
vector<Employee*> childEngineers = helper(child);
// Collect all valid engineers returned from the subtree
processedSubtree.insert(processedSubtree.end(),
childEngineers.begin(),
childEngineers.end());
}
// 2. Process current node
if (current->isEngineer) {
// SCENARIO: Engineer -> Keep
// Create a NEW node (Deep Copy)
Employee* newEngNode = new Employee(current->id, true);
// Adopt the collected subtree
newEngNode->reportees = processedSubtree;
// Return this node as the single result
engineersList.push_back(newEngNode);
} else {
// SCENARIO: Non-Engineer -> Dissolve
// Do NOT create a node.
// Pass the processed children upwards to the parent
engineersList = processedSubtree;
}
return engineersList;
}
};