The Minimum Height Trees problem asks you to find all possible roots of an undirected tree such that the tree has the minimum possible height.
Given an integer n
representing the number of nodes labeled from 0
to n-1
, and a list of undirected edges edges
where each edge is a pair [u, v]
, your task is to return a list of all nodes that can be the root of a tree with the smallest height.
Constraints:
1 <= n <= 2 * 10^4
edges.length == n - 1
0 <= u, v < n
u != v
[u, v]
are unique (no duplicate edges).At first glance, it may seem that you could try every node as a root and compute the height of the resulting tree, then pick those with the minimum height. However, this brute-force approach would be very inefficient, especially for large trees, since calculating the height for each node as root is expensive.
To optimize, we need to recognize that a tree's height is minimized when the root is located in the "center" of the tree. Think of the tree as a network of nodes – the center is the node (or nodes) that minimizes the farthest distance to any leaf. In graph theory, these are called the tree centers.
Instead of trying every possibility, we can iteratively remove all leaves (nodes with only one connection) layer by layer, similar to peeling an onion, until only one or two nodes remain. These are the centers of the tree and are the roots of the Minimum Height Trees.
Let's break down the optimized algorithm step by step:
This approach is efficient because each edge and node is visited and removed at most once, leading to a linear time solution.
Example:
n = 6
, edges = [[0,1],[0,2],[0,3],[3,4],[4,5]]
If there were two nodes left at the end, both would be returned.
n
.The optimized solution is efficient and scalable for large trees.
from collections import deque, defaultdict
class Solution:
def findMinHeightTrees(self, n, edges):
if n == 1:
return [0]
graph = defaultdict(set)
for u, v in edges:
graph[u].add(v)
graph[v].add(u)
leaves = [i for i in range(n) if len(graph[i]) == 1]
remaining = n
while remaining > 2:
remaining -= len(leaves)
new_leaves = []
for leaf in leaves:
neighbor = graph[leaf].pop()
graph[neighbor].remove(leaf)
if len(graph[neighbor]) == 1:
new_leaves.append(neighbor)
leaves = new_leaves
return leaves
#include <vector>
#include <unordered_set>
using namespace std;
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if (n == 1) return {0};
vector<unordered_set<int>> graph(n);
for (auto& edge : edges) {
graph[edge[0]].insert(edge[1]);
graph[edge[1]].insert(edge[0]);
}
vector<int> leaves;
for (int i = 0; i < n; ++i)
if (graph[i].size() == 1) leaves.push_back(i);
int remaining = n;
while (remaining > 2) {
remaining -= leaves.size();
vector<int> new_leaves;
for (int leaf : leaves) {
int neighbor = *graph[leaf].begin();
graph[neighbor].erase(leaf);
if (graph[neighbor].size() == 1)
new_leaves.push_back(neighbor);
}
leaves = new_leaves;
}
return leaves;
}
};
import java.util.*;
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
if (n == 1) return Collections.singletonList(0);
List<Set<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; ++i) graph.add(new HashSet<>());
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
List<Integer> leaves = new ArrayList<>();
for (int i = 0; i < n; ++i)
if (graph.get(i).size() == 1) leaves.add(i);
int remaining = n;
while (remaining > 2) {
remaining -= leaves.size();
List<Integer> newLeaves = new ArrayList<>();
for (int leaf : leaves) {
int neighbor = graph.get(leaf).iterator().next();
graph.get(neighbor).remove(leaf);
if (graph.get(neighbor).size() == 1)
newLeaves.add(neighbor);
}
leaves = newLeaves;
}
return leaves;
}
}
var findMinHeightTrees = function(n, edges) {
if (n === 1) return [0];
const graph = Array.from({length: n}, () => new Set());
for (const [u, v] of edges) {
graph[u].add(v);
graph[v].add(u);
}
let leaves = [];
for (let i = 0; i < n; ++i) {
if (graph[i].size === 1) leaves.push(i);
}
let remaining = n;
while (remaining > 2) {
remaining -= leaves.length;
const newLeaves = [];
for (const leaf of leaves) {
const neighbor = Array.from(graph[leaf])[0];
graph[neighbor].delete(leaf);
if (graph[neighbor].size === 1) newLeaves.push(neighbor);
}
leaves = newLeaves;
}
return leaves;
};
The Minimum Height Trees problem is elegantly solved by leveraging the properties of trees and centers. Rather than brute-forcing every root, we iteratively remove leaves, converging on the central node(s) that minimize the tree's height. This approach is efficient, intuitive, and scales well even for large trees, making it a great example of how understanding the structure of a problem leads to optimal solutions.