Max Height Calculation — Problem Statement & Solution Guide
Quick Answer & Algorithm Key Takeaway
Master coding challenges related to Recursion and solve the Max Height Calculation problem optimally.
O(N)O(H)Problem Description
Given a rooted tree with N distinct nodes, compute its maximum height measured as the number of nodes on the longest root‑to‑leaf path (both endpoints inclusive). The tree is supplied as follows: the first line contains the integer N. Each of the next N lines describes one node. A line starts with the node's identifier, followed by an integer C indicating how many direct children it has, then C identifiers of those children separated by spaces. All identifiers are unique and the structure forms a single rooted tree (exactly one node has no parent). Output a single integer – the tree's maximum height.
DSA Pattern Breakdown
DSA Pattern Breakdown
"Max Height Calculation"
WHY DOES IT MATTER?
Computing tree height is a foundational pattern for any problem that requires understanding hierarchical depth, such as evaluating expression trees, file system depth, or organizational charts. Mastery of this pattern demonstrates a candidate's ability to reason about recursion, graph traversal, and optimal substructure.
OPTIMIZATION CHALLENGE
The key insight is to avoid recomputing child heights by propagating results upward in a single DFS pass, turning an exponential or quadratic brute force into a linear O(N) solution.
REAL-WORLD CONNECTION
Think of a corporate org chart: the CEO is the root, and the longest chain of management levels from CEO to the most junior employee mirrors the tree's height. In distributed tracing, the deepest span sequence reflects the maximum call depth, analogous to tree height computation.
When coding, first build an adjacency list, locate the root with a set difference, then write a clean recursive function that returns height; add a visited guard only if the input might contain cycles, but a proper tree guarantees acyclicity.
COMPLEXITY AT A GLANCE
O(N)O(H)Core Theory — Why This Approach?
The problem of computing a tree's maximum height is a classic example of depth‑first recursion on hierarchical data structures. A naive solution that repeatedly scans the entire node list for each node leads to quadratic time because each subtree is recomputed from scratch, which quickly becomes infeasible for large N (10⁵+). The optimal paradigm treats the tree as a directed acyclic graph rooted at the unique root, and performs a single post‑order traversal: for each node, the height is 1 plus the maximum height among its children. This leverages the optimal substructure property—sub‑problems (heights of sub‑trees) are independent and can be solved once, then combined. By memoizing or simply propagating results upward during recursion, the algorithm runs in linear time, O(N), and uses only O(H) auxiliary stack space, where H is the tree height.
Recursion naturally mirrors the definition of height: the height of a leaf is 1, and any internal node's height is 1 + max(child heights). Implementing this with a depth‑first search (DFS) ensures each edge is visited exactly once. The adjacency list representation—mapping each node to its list of children—provides O(1) access to a node's descendants, avoiding costly searches. This approach also gracefully handles unbalanced trees, as the recursion depth adapts to the actual height rather than the total node count.
Why naive approaches fail: iterating over all nodes for each depth level or recomputing child heights repeatedly results in O(N²) time, which exceeds typical time limits for N up to 10⁵. The optimal linear solution eliminates redundant work by visiting each node a single time, making it scalable for production‑grade datasets and interview constraints.
Interview Questions on This Problem
Q1How would you compute the maximum height of a tree given only a parent‑to‑children map without an explicit root identifier?
First, identify the root by finding the node that never appears as a child (using a set difference). Then run a DFS from that root, returning 1 + max(child heights) for each node; the final return value is the tree's maximum height.
Q2In a distributed system where each service reports its downstream dependencies as a list, how can you detect the longest call chain efficiently?
Model the services as nodes in a directed acyclic graph and apply the same DFS height calculation; the longest call chain corresponds to the maximum height, computed in O(V+E) time with memoization to avoid recomputation across services.
Q3Why might a recursive solution cause a stack overflow on very deep trees, and how can you mitigate it in a coding interview?
Recursion depth equals tree height; for skewed trees with N≈10⁵, the call stack may exceed language limits. Mitigate by converting the DFS to an explicit stack (iterative post‑order) or by using tail‑recursion optimization where the language supports it.
Examples
Input
5 1 2 2 3 2 1 4 3 0 4 1 5 5 0
Output
1
Explanation: Node 1 is the root (no parent). The longest path is 1→2→4→5, which contains 4 nodes, so the height is 4.
Input
3 10 1 20 20 1 30 30 0
Output
1
Explanation: Root is 10. The only root‑to‑leaf chain is 10→20→30, comprising 3 nodes; therefore the height equals 3.
Input
1 42 0
Output
1
Explanation: A single node forms a tree of height 1 because the root is also the leaf.
Constraints
- 1 <= N <= 100000
- Node identifiers are distinct integers in the range [1, 10^9]
- The sum of all child counts equals N-1, guaranteeing a single connected tree
- Recursion depth may reach N, so an iterative or tail‑recursive solution is recommended
Optimal Approach & Strategy
Perform a single depth‑first traversal from the root, returning 1 + max(child heights) for each node, achieving O(N) time.
Brute Force Approach
Repeatedly compute the height of every node by traversing its entire subtree each time, leading to O(N²) time.
Code Solutions
function maxHeight(children, root) {
if (!children[root] || children[root].length === 0) {
return 1;
}
let maxChildHeight = 0;
for (const child of children[root]) {
const childHeight = maxHeight(children, child);
maxChildHeight = Math.max(maxChildHeight, childHeight);
}
return 1 + maxChildHeight;
}
const fs = require('fs');
const data = fs.readFileSync(0, 'utf8').trim().split('\n');
let index = 0;
function readInt() {
return parseInt(data[index++]);
}
const N = readInt();
const children = {};
const allNodes = new Set();
const childNodes = new Set();
for (let i = 0; i < N; i++) {
const node = readInt();
const C = readInt();
const kids = [];
for (let j = 0; j < C; j++) {
const child = readInt();
kids.push(child);
childNodes.add(child);
}
children[node] = kids;
allNodes.add(node);
}
let root = -1;
for (const node of allNodes) {
if (!childNodes.has(node)) {
root = node;
break;
}
}
console.log(maxHeight(children, root));#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
using namespace std;
int maxHeight(unordered_map<int, vector<int>>& children, int root) {
if (children.find(root) == children.end() || children[root].empty()) {
return 1;
}
int maxChildHeight = 0;
for (int child : children[root]) {
int childHeight = maxHeight(children, child);
maxChildHeight = max(maxChildHeight, childHeight);
}
return 1 + maxChildHeight;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N;
cin >> N;
unordered_map<int, vector<int>> children;
unordered_set<int> allNodes;
unordered_set<int> childNodes;
for (int i = 0; i < N; i++) {
int node, C;
cin >> node >> C;
vector<int> kids(C);
for (int j = 0; j < C; j++) {
cin >> kids[j];
childNodes.insert(kids[j]);
}
children[node] = kids;
allNodes.insert(node);
}
int root = -1;
for (int node : allNodes) {
if (childNodes.find(node) == childNodes.end()) {
root = node;
break;
}
}
cout << maxHeight(children, root) << endl;
return 0;
}import java.util.*;
public class Main {
public static int maxHeight(Map<Integer, List<Integer>> children, int root) {
if (!children.containsKey(root) || children.get(root).isEmpty()) {
return 1;
}
int maxChildHeight = 0;
for (int child : children.get(root)) {
int childHeight = maxHeight(children, child);
maxChildHeight = Math.max(maxChildHeight, childHeight);
}
return 1 + maxChildHeight;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
Map<Integer, List<Integer>> children = new HashMap<>();
Set<Integer> allNodes = new HashSet<>();
Set<Integer> childNodes = new HashSet<>();
for (int i = 0; i < N; i++) {
int node = scanner.nextInt();
int C = scanner.nextInt();
List<Integer> kids = new ArrayList<>();
for (int j = 0; j < C; j++) {
int child = scanner.nextInt();
kids.add(child);
childNodes.add(child);
}
children.put(node, kids);
allNodes.add(node);
}
int root = -1;
for (int node : allNodes) {
if (!childNodes.contains(node)) {
root = node;
break;
}
}
System.out.println(maxHeight(children, root));
scanner.close();
}
}import sys
def max_height(children, root):
if root not in children or len(children[root]) == 0:
return 1
max_child_height = 0
for child in children[root]:
child_height = max_height(children, child)
max_child_height = max(max_child_height, child_height)
return 1 + max_child_height
def main():
input_data = sys.stdin.read().split()
idx = 0
def read_int():
nonlocal idx
val = int(input_data[idx])
idx += 1
return val
N = read_int()
children = {}
all_nodes = set()
child_nodes = set()
for _ in range(N):
node = read_int()
C = read_int()
kids = []
for _ in range(C):
child = read_int()
kids.append(child)
child_nodes.add(child)
children[node] = kids
all_nodes.add(node)
root = -1
for node in all_nodes:
if node not in child_nodes:
root = node
break
print(max_height(children, root))
if __name__ == "__main__":
main()function maxHeight(children, root) {
if (!children[root] || children[root].length === 0) {
return 1;
}
let maxChildHeight = 0;
for (const child of children[root]) {
const childHeight = maxHeight(children, child);
maxChildHeight = Math.max(maxChildHeight, childHeight);
}
return 1 + maxChildHeight;
}
const fs = require('fs');
const data = fs.readFileSync(0, 'utf8').trim().split('\n');
let index = 0;
function readInt() {
return parseInt(data[index++]);
}
const N = readInt();
const children = {};
const allNodes = new Set();
const childNodes = new Set();
for (let i = 0; i < N; i++) {
const node = readInt();
const C = readInt();
const kids = [];
for (let j = 0; j < C; j++) {
const child = readInt();
kids.push(child);
childNodes.add(child);
}
children[node] = kids;
allNodes.add(node);
}
let root = -1;
for (const node of allNodes) {
if (!childNodes.has(node)) {
root = node;
break;
}
}
console.log(maxHeight(children, root));Asked in Top Tech Interviews
Solve in Interative Editor
Ready to test your code? Open our built-in compiler, run custom test suites, and see detailed complexity analysis reports instantly.