Question:
A tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any connected graph without simple cycles is a tree.
Given a tree of n
 nodes labelled from 0
 to n - 1
, and an array of n - 1
 edges
 where edges[i] = [ai, bi]
 indicates that there is an undirected edge between the two nodes ai
 and bi
 in the tree, you can choose any node of the tree as the root. When you select a node x
 as the root, the result tree has height h
. Among all possible rooted trees, those with minimum height (i.e. min(h)
) are called minimum height trees (MHTs).
Return a list of all MHTs’ root labels. You can return the answer in any order.
The height of a rooted tree is the number of edges on the longest downward path between the root and a leaf.
Example 1:
Input: n = 4, edges = [[1,0],[1,2],[1,3]] Output: [1] Explanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT.
Example 2:
Open: Screenshot 2024-06-21 at 4.38.40 PM.png
Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] Output: [3,4]
Solution:
- Brute Force: (Ran BFS for every node while maintaining a array which contains min distance for each node)
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
List<Integer> minDistList = new ArrayList<>();
ArrayList<Integer>[] graph = new ArrayList[n];
for(int i = 0; i < n; i++){
graph[i] = new ArrayList<>();
}
for(int[] edge: edges){
int u = edge[0];
int v = edge[1];
graph[u].add(v);
graph[v].add(u);
}
for(int i = 0; i < n; i++){
int minDist = Integer.MIN_VALUE;
Queue<int[]> q = new LinkedList<>();
boolean[] vis = new boolean[n];
q.offer(new int[]{i, 0});
while(!q.isEmpty()){
int[] curr = q.remove();
int currV = curr[0];
int dist = curr[1];
if(vis[currV] == true) continue;
vis[currV] = true;
minDist = Math.max(minDist, dist);
for(int edge: graph[currV]){
if(!vis[edge]){
q.offer(new int[]{edge, dist + 1});
}
}
}
minDistList.add(minDist);
}
int minVal = Integer.MAX_VALUE;
for(int i = 0; i < minDistList.size(); i++){
minVal = Math.min(minVal, minDistList.get(i));
}
List<Integer> ans = new ArrayList<>();
for(int i = 0; i < minDistList.size(); i++){
if(minDistList.get(i) == minVal){
ans.add(i);
}
}
return ans;
}
}
- Optimized solution
Observations and Intuition:
- In a tree, the minimum height is achieved when the root is chosen such that the heights of its subtrees are as balanced as possible.
- The nodes that can be the roots of MHTs are the centroids of the tree.
- The centroids of a tree are the nodes that minimize the maximum distance to any other node in the tree.
- In a tree, there can be at most two centroids.
- We can iteratively remove the leaves (nodes with only one neighbor) until we are left with one or two nodes, which will be the centroids.
Optimized Approach (Topological Sorting):
-
Create an adjacency list to represent the tree.
-
Initialize a list to store the degrees of each node (number of neighbors).
-
Initialize a queue to perform a topological sorting-like process.
-
Enqueue all the leaves (nodes with only one neighbor) into the queue.
-
While the number of remaining nodes is greater than 2:
-
Dequeue a node from the queue.
-
Decrement the degree of its neighbor.
-
If the neighbor becomes a leaf (degree becomes 1), enqueue it.
-
The remaining one or two nodes in the queue are the roots of the MHTs. Time complexity: O(n), where n is the number of nodes. Space complexity: O(n) to store the adjacency list and the queue.
public class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
// Special case for a single node
if (n == 1) {
return Collections.singletonList(0);
}
// Initialize the graph as an array of ArrayLists
List<Integer>[] graph = new ArrayList[n];
for (int i = 0; i < n; i++) {
graph[i] = new ArrayList<>();
}
// Initialize the in-degrees array
int[] inDegrees = new int[n];
// Build the graph and populate in-degrees
for (int[] edge : edges) {
int u = edge[0];
int v = edge[1];
graph[u].add(v);
graph[v].add(u);
inDegrees[u]++;
inDegrees[v]++;
}
// Initialize the queue with leaf nodes
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegrees[i] == 1) {
queue.offer(i);
}
}
// Perform BFS to remove leaves layer by layer
List<Integer> remainingNodes = new ArrayList<>();
while (!queue.isEmpty()) {
remainingNodes = new ArrayList<>(); // Nodes remaining after the current layer is removed
int size = queue.size();
for (int i = 0; i < size; i++) {
int leaf = queue.poll();
remainingNodes.add(leaf);
for (int neighbor : graph[leaf]) {
inDegrees[neighbor]--;
if (inDegrees[neighbor] == 1) { // If it becomes a leaf, add to queue
queue.offer(neighbor);
}
}
}
}
// Remaining nodes are the roots of the MHTs
return remainingNodes;
}
}