Loading problems...
TL;DR: The optimal solution uses a BFS-based topological sort approach to iteratively remove leaf nodes (degree 1) layer by layer until only the graph's centroids remain.
The problem asks us to find the root node(s) that minimize the height of a tree. Given an undirected graph that is guaranteed to be a tree (no cycles, fully connected), we can select any node as the root. Rooting the tree at different nodes results in different heights. We need to identify all nodes that, when chosen as the root, produce the minimum possible height. These are the roots of the Minimum Height Trees (MHTs).
This is a popular interview question, often referred to as finding the "centroid" of a graph.
A naive approach is to simulate the process described in the problem statement literally. We can iterate through every node in the graph, designate it as the root, and perform a graph traversal (like BFS or DFS) to calculate the height of the resulting tree.
min_height to infinity and a list result to store root candidates.i from 0 to .n-1i, run a Breadth-First Search (BFS) starting from i to find the maximum distance to any leaf. This distance is the tree height.min_height, update min_height and reset result to [i].min_height, append i to result.The time complexity of a single BFS traversal is . Since the graph is a tree, , so one traversal is . We perform this traversal for every node, resulting in a total time complexity of .
With constraints allowing up to , an solution performs roughly operations, which exceeds the typical limit of operations per second. This approach will result in a Time Limit Exceeded (TLE) error on large test cases.
The intuition for the optimal solution comes from analyzing the structure of a tree. The nodes that minimize height are the "centroids" of the tree—the nodes closest to the center.
Consider the longest path in the tree (the diameter). The root of a Minimum Height Tree must be the middle vertex (or vertices) of this longest path. If we root the tree at a leaf node (an endpoint of a path), the height is maximized. Therefore, the optimal roots are as far away from the leaf nodes as possible.
This leads to a "peeling onion" strategy. If we remove all current leaf nodes from the tree, the centroids remain the same, but the tree becomes smaller. We can repeat this process—trimming the outer layer of leaves—until we are left with a core of one or two nodes. These remaining nodes are the roots of the MHTs.
This is structurally identical to Kahn's Algorithm for topological sorting:
Visual Description: Imagine the tree as a physical structure. The leaves are the outermost layer. In the first iteration of our algorithm, we identify all leaves and "prune" them. This exposes a new layer of nodes that have effectively become leaves in the reduced tree. We repeat this pruning process layer by layer. The pointers move inward from the periphery towards the center. The last batch of nodes remaining in our queue represents the geometric center of the graph.

We implement the solution using a strategy derived from Kahn's Algorithm:
remaining_nodes, initially set to .remaining_nodes > 2, perform a level-order traversal (BFS):
size).size from remaining_nodes.Let's trace the algorithm with Example 1: n = 4, edges = [[1,0],[1,2],[1,3]].
Initialization:
{0: [1], 1: [0, 2, 3], 2: [1], 3: [1]}[1, 3, 1, 1] (Node 0 has degree 1, Node 1 has degree 3, etc.)Queue Setup:
0, 2, 3.[0, 2, 3]Iteration 1:
remaining_nodes > 2 is true (4 > 2).remaining_nodes: .1. Decrement degree of 1 from 3 to 2.1. Decrement degree of 1 from 2 to 1. Degree is 1, so enqueue 1.1. Decrement degree of 1 from 1 to 0.[1].Termination:
remaining_nodes > 2 is false (1 is not > 2).Output:
[1].The correctness relies on the property of the tree diameter. The longest path between any two nodes in a tree is its diameter. The Minimum Height Tree roots must lie at the midpoint of this diameter.
Trimming leaves from the tree is equivalent to removing the endpoints of the diameter. This operation shortens the diameter by 2 (1 from each end) but does not change the midpoint. By repeatedly removing leaves, we continuously shrink the diameter until the midpoint is isolated. Since a path can have at most one or two middle vertices, the algorithm is guaranteed to converge to 1 or 2 nodes.
cppclass Solution { public: vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) { if (n == 1) return {0}; // Edge case: single node tree vector<vector<int>> adj(n); vector<int> degree(n, 0); // Build graph and compute degrees for (const auto& edge : edges) { adj[edge[0]].push_back(edge[1]); adj[edge[1]].push_back(edge[0]); degree[edge[0]]++; degree[edge[1]]++; } // Initialize queue with initial leaves queue<int> q; for (int i = 0; i < n; ++i) { if (degree[i] == 1) { q.push(i); } } int remaining_nodes = n; // Trim leaves until 2 or fewer nodes remain while (remaining_nodes > 2) { int level_size = q.size(); remaining_nodes -= level_size; for (int i = 0; i < level_size; ++i) { int leaf = q.front(); q.pop(); for (int neighbor : adj[leaf]) { degree[neighbor]--; if (degree[neighbor] == 1) { q.push(neighbor); } } } } vector<int> result; while (!q.empty()) { result.push_back(q.front()); q.pop(); } return result; } };
javaimport java.util.*; class Solution { public List<Integer> findMinHeightTrees(int n, int[][] edges) { if (n == 1) return Collections.singletonList(0); // Build graph using adjacency list List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } int[] degree = new int[n]; for (int[] edge : edges) { adj.get(edge[0]).add(edge[1]); adj.get(edge[1]).add(edge[0]); degree[edge[0]]++; degree[edge[1]]++; } // Initialize queue with leaves Queue<Integer> queue = new LinkedList<>(); for (int i = 0; i < n; i++) { if (degree[i] == 1) { queue.offer(i); } } int remainingNodes = n; // Process layers while (remainingNodes > 2) { int levelSize = queue.size(); remainingNodes -= levelSize; for (int i = 0; i < levelSize; i++) { int leaf = queue.poll(); for (int neighbor : adj.get(leaf)) { degree[neighbor]--; if (degree[neighbor] == 1) { queue.offer(neighbor); } } } } return new ArrayList<>(queue); } }
pythonfrom collections import deque class Solution: def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: if n == 1: return [0] # Build adjacency list and degree array adj = [[] for _ in range(n)] degree = [0] * n for u, v in edges: adj[u].append(v) adj[v].append(u) degree[u] += 1 degree[v] += 1 # Initialize queue with all leaf nodes queue = deque([i for i in range(n) if degree[i] == 1]) remaining_nodes = n # Process until we reach the centroid(s) while remaining_nodes > 2: level_size = len(queue) remaining_nodes -= level_size for _ in range(level_size): leaf = queue.popleft() for neighbor in adj[leaf]: degree[neighbor] -= 1 if degree[neighbor] == 1: queue.append(neighbor) return list(queue)
Time Complexity:
Space Complexity:
The Graph BFS - Topological Sort pattern (specifically processing nodes by degree) is highly reusable. Similar logic applies to:
In all these problems, the core mechanism is maintaining an in-degree (or degree) count and using a queue to process nodes as their dependencies are resolved.
Here are common pitfalls when solving LeetCode 310:
Why does the naive BFS solution TLE?
Why can't we just pick a random node or the node with the highest degree?
Forgetting the edge case where n = 1.
while remaining_nodes > 2 implicitly assumes the graph has leaves with degree 1.[] instead of [0].Using degree > 0 instead of degree == 1 for queue insertion.