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:
Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] Output: [3,4]
Constraints:
1 <= n <= 2 * 104
edges.length == n - 1
0 <= ai, bi < n
ai != bi
(ai, bi)
are distinct.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The goal is to find the 'center' of a network by trying every possible node as the root. For each potential root, we determine the height of the resulting tree. The brute force approach involves checking every node to see if it produces the smallest possible height.
Here's how the algorithm would work step-by-step:
def find_minimum_height_trees_brute_force(number_of_nodes, edges):
if number_of_nodes <= 0:
return []
if number_of_nodes == 1:
return [0]
graph = [[] for _ in range(number_of_nodes)]
for start_node, end_node in edges:
graph[start_node].append(end_node)
graph[end_node].append(start_node)
minimum_height = float('inf')
minimum_height_trees = []
for root_node in range(number_of_nodes):
# Perform a Breadth-First Search to calculate the height of the tree
queue = [(root_node, 0)]
visited = {root_node}
current_height = 0
while queue:
node, height = queue.pop(0)
current_height = max(current_height, height)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, height + 1))
# Check if the current height is the minimum height seen so far.
if current_height < minimum_height:
minimum_height = current_height
minimum_height_trees = [root_node]
elif current_height == minimum_height:
minimum_height_trees.append(root_node)
return minimum_height_trees
The most efficient strategy resembles peeling layers from an onion. We iteratively remove the leaves (nodes with only one connection) of the tree until we're left with either one or two center nodes, which will be the roots of our minimum height trees.
Here's how the algorithm would work step-by-step:
def find_minimum_height_trees(number_of_nodes, edges):
if number_of_nodes <= 0:
return []
if number_of_nodes == 1:
return [0]
adjacency_list = [set() for _ in range(number_of_nodes)]
for start_node, end_node in edges:
adjacency_list[start_node].add(end_node)
adjacency_list[end_node].add(start_node)
# Identify initial leaves, which have only one connection.
leaves = [node for node in range(number_of_nodes) if len(adjacency_list[node]) == 1]
remaining_nodes = number_of_nodes
# Iteratively remove leaves until we have at most 2 nodes.
while remaining_nodes > 2:
remaining_nodes -= len(leaves)
new_leaves = []
# Process each leaf and update the adjacency list.
for leaf in leaves:
neighbor = adjacency_list[leaf].pop()
adjacency_list[neighbor].remove(leaf)
# If a neighbor becomes a leaf, add it to the next layer of leaves.
if len(adjacency_list[neighbor]) == 1:
new_leaves.append(neighbor)
leaves = new_leaves
# The remaining nodes are the roots of the MHTs.
return leaves
Case | How to Handle |
---|---|
Empty graph (n=0) | Return an empty list as there are no nodes or trees to compute. |
Single node graph (n=1) | Return a list containing only node 0, as it's the only possible root. |
Two node graph (n=2) | Return a list containing both node 0 and 1, as either can be the root with minimum height 0. |
Disconnected graph | The algorithm will only process the connected component containing node 0, which is incorrect; a check is needed to ensure the entire graph is connected and an error or empty list should be returned if disconnected. |
Graph with cycles | The algorithm works correctly with cycles, as the degree-based removal process eventually reduces the graph to its 'center'. |
Large graph (n approaching memory limits) | The space complexity of the adjacency list might become an issue; consider alternative representations for extremely large graphs, and ensure appropriate data types are used to prevent integer overflow when calculating degrees. |
Star graph (one node connected to all others) | The algorithm correctly identifies the central node as the root of the minimum height tree. |
Complete graph (every node connected to every other node) | Any node can serve as the root, and the algorithm should correctly return a list of all nodes. |