You are given an undirected tree with n
nodes, labeled from 0 to n - 1
. The tree is represented by an array of n - 1
edges, where each edges[i] = [a_i, b_i]
indicates an undirected edge between nodes a_i
and b_i
. A minimum height tree (MHT) is a rooted tree formed by choosing any node as the root such that the height of the tree is minimized.
Your task is to write a function that finds all the roots that result in MHTs. The height of a rooted tree is defined as the number of edges on the longest downward path between the root and a leaf. The function should return a list of the labels of the root nodes that produce MHTs. The order of the root labels in the output list does not matter.
Example 1:
n = 4, edges = [[1,0],[1,2],[1,3]]
Output: [1]
Explanation: If node 1
is chosen as the root, the height of the tree is 1, which is the minimum possible height.
Example 2:
n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
Output: [3, 4]
Explanation: If node 3
is chosen as the root, the height of the tree is 2. If node 4
is chosen as the root, the height of the tree is also 2. These are the minimum possible heights for this tree.
Constraints:
1 <= n <= 2 * 10^4
edges.length == n - 1
0 <= a_i, b_i < n
a_i != b_i
(a_i, b_i)
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. |