Bird
Raised Fist0
Interview Preptopological-sortmediumAmazonGoogle

Minimum Height Trees (Centroid by Trimming)

Choose your preparation mode4 modes available
🎯
Minimum Height Trees (Centroid by Trimming)
mediumGRAPHAmazonGoogle

Imagine you want to build a communication network with the minimum possible maximum distance from a central hub to any node. Finding the best hub(s) is like finding the tree's center(s).

💡 This problem involves understanding tree structures and how to find nodes that minimize the height of the tree when chosen as roots. Beginners often struggle because it requires insight into graph properties and efficient pruning techniques rather than straightforward traversal.
📋
Problem Statement

Given an undirected graph with n nodes labeled from 0 to n-1, and a list of edges where each edge connects two nodes, find all the roots of Minimum Height Trees (MHTs). A Minimum Height Tree is a rooted tree with the minimum possible height among all possible rooted trees formed from the graph. Return a list of all such root labels.

1 ≤ n ≤ 10^5edges.length = n - 10 ≤ edges[i][0], edges[i][1] < nAll pairs (edges[i][0], edges[i][1]) are distinctThe input graph is guaranteed to be a tree (connected and acyclic)
💡
Example
Input"n = 4, edges = [[1,0],[1,2],[1,3]]"
Output[1]

Choosing node 1 as root results in a tree of height 1, which is minimal.

Input"n = 6, edges = [[0,3],[1,3],[2,3],[4,3],[5,4]]"
Output[3,4]

Both nodes 3 and 4 can be roots of MHTs with height 2.

  • n = 1, edges = [] → [0] single node tree
  • n = 2, edges = [[0,1]] → [0,1] both nodes are MHT roots
  • Star shaped tree with one center and all leaves → center node only
  • Line shaped tree (chain) → two middle nodes as roots if even length
⚠️
Common Mistakes
Not handling single node tree (n=1) separately

Code may return empty list or error

✅ Add explicit check for n == 1 and return [0]

Removing leaves but not updating degrees correctly

Infinite loop or incorrect results

✅ Decrease neighbor degrees and add new leaves only when degree becomes 1

Using BFS from each node without pruning

Time limit exceeded on large inputs

✅ Use leaf trimming approach instead of brute force BFS from each node

Returning more than two nodes as centroids

Incorrect output, since MHT roots are at most two

✅ Stop trimming when remaining nodes ≤ 2 and return them

🧠
Brute Force (Calculate Height for Each Node as Root)
💡 This approach exists to build intuition by directly simulating the problem definition, even though it is inefficient. It helps understand what 'minimum height' means by explicitly computing heights.

Intuition

For each node, treat it as the root and compute the height of the tree by BFS or DFS. The nodes with the smallest height are the answer.

Algorithm

  1. For each node from 0 to n-1:
  2. Perform BFS/DFS to find the height of the tree rooted at that node.
  3. Track the minimum height found and the nodes that achieve it.
  4. Return all nodes with the minimum height.
💡 The main challenge is that computing height for each root requires traversing the entire tree repeatedly, which is expensive.
</>
Code
from collections import deque

def findMinHeightTrees(n, edges):
    if n == 1:
        return [0]

    graph = [[] for _ in range(n)]
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)

    def bfs(root):
        visited = [False] * n
        queue = deque([(root, 0)])
        visited[root] = True
        max_depth = 0
        while queue:
            node, depth = queue.popleft()
            max_depth = max(max_depth, depth)
            for nei in graph[node]:
                if not visited[nei]:
                    visited[nei] = True
                    queue.append((nei, depth + 1))
        return max_depth

    min_height = float('inf')
    res = []
    for i in range(n):
        height = bfs(i)
        if height < min_height:
            min_height = height
            res = [i]
        elif height == min_height:
            res.append(i)
    return res

# Driver code
if __name__ == '__main__':
    print(findMinHeightTrees(4, [[1,0],[1,2],[1,3]]))  # Output: [1]
Line Notes
if n == 1:Handle trivial single-node tree quickly
graph = [[] for _ in range(n)]Build adjacency list for efficient neighbor lookup
def bfs(root):Define BFS to compute height from a given root
queue = deque([(root, 0)])Initialize queue with root node and depth 0
max_depth = max(max_depth, depth)Track maximum depth reached during BFS
for i in range(n):Try each node as root to find minimum height
height = bfs(i)Compute height for current root candidate
if height < min_height:Update minimum height and result list accordingly
import java.util.*;

public class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        if (n == 1) return Collections.singletonList(0);

        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
        for (int[] edge : edges) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
        }

        int minHeight = Integer.MAX_VALUE;
        List<Integer> res = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            int height = bfs(i, graph, n);
            if (height < minHeight) {
                minHeight = height;
                res.clear();
                res.add(i);
            } else if (height == minHeight) {
                res.add(i);
            }
        }
        return res;
    }

    private int bfs(int root, List<List<Integer>> graph, int n) {
        boolean[] visited = new boolean[n];
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{root, 0});
        visited[root] = true;
        int maxDepth = 0;

        while (!queue.isEmpty()) {
            int[] curr = queue.poll();
            int node = curr[0], depth = curr[1];
            maxDepth = Math.max(maxDepth, depth);
            for (int nei : graph.get(node)) {
                if (!visited[nei]) {
                    visited[nei] = true;
                    queue.offer(new int[]{nei, depth + 1});
                }
            }
        }
        return maxDepth;
    }

    // Driver code
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.findMinHeightTrees(4, new int[][]{{1,0},{1,2},{1,3}})); // [1]
    }
}
Line Notes
if (n == 1) return Collections.singletonList(0);Quickly handle single node tree
List<List<Integer>> graph = new ArrayList<>();Create adjacency list for graph
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());Initialize adjacency lists for each node
queue.offer(new int[]{root, 0});Start BFS from root with depth 0
maxDepth = Math.max(maxDepth, depth);Keep track of max depth during BFS
for (int i = 0; i < n; i++) {Try each node as root to find minimal height
if (height < minHeight) {Update minimum height and result list
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;

class Solution {
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if (n == 1) return {0};

        vector<vector<int>> graph(n);
        for (auto& e : edges) {
            graph[e[0]].push_back(e[1]);
            graph[e[1]].push_back(e[0]);
        }

        int minHeight = INT_MAX;
        vector<int> res;

        for (int i = 0; i < n; i++) {
            int height = bfs(i, graph, n);
            if (height < minHeight) {
                minHeight = height;
                res.clear();
                res.push_back(i);
            } else if (height == minHeight) {
                res.push_back(i);
            }
        }
        return res;
    }

private:
    int bfs(int root, vector<vector<int>>& graph, int n) {
        vector<bool> visited(n, false);
        queue<pair<int,int>> q;
        q.push({root, 0});
        visited[root] = true;
        int maxDepth = 0;

        while (!q.empty()) {
            auto [node, depth] = q.front(); q.pop();
            maxDepth = max(maxDepth, depth);
            for (int nei : graph[node]) {
                if (!visited[nei]) {
                    visited[nei] = true;
                    q.push({nei, depth + 1});
                }
            }
        }
        return maxDepth;
    }
};

// Driver code
int main() {
    Solution sol;
    vector<vector<int>> edges = {{1,0},{1,2},{1,3}};
    vector<int> res = sol.findMinHeightTrees(4, edges);
    for (int r : res) cout << r << ' '; // Output: 1
    cout << endl;
    return 0;
}
Line Notes
if (n == 1) return {0};Handle trivial single-node tree quickly
vector<vector<int>> graph(n);Build adjacency list for efficient neighbor lookup
q.push({root, 0});Initialize BFS queue with root and depth 0
maxDepth = max(maxDepth, depth);Track maximum depth reached during BFS
for (int i = 0; i < n; i++) {Try each node as root to find minimum height
int height = bfs(i, graph, n);Compute height for current root candidate
if (height < minHeight) {Update minimum height and result list accordingly
function findMinHeightTrees(n, edges) {
    if (n === 1) return [0];

    const graph = Array.from({length: n}, () => []);
    for (const [u, v] of edges) {
        graph[u].push(v);
        graph[v].push(u);
    }

    function bfs(root) {
        const visited = new Array(n).fill(false);
        const queue = [[root, 0]];
        visited[root] = true;
        let maxDepth = 0;

        while (queue.length > 0) {
            const [node, depth] = queue.shift();
            maxDepth = Math.max(maxDepth, depth);
            for (const nei of graph[node]) {
                if (!visited[nei]) {
                    visited[nei] = true;
                    queue.push([nei, depth + 1]);
                }
            }
        }
        return maxDepth;
    }

    let minHeight = Infinity;
    const res = [];
    for (let i = 0; i < n; i++) {
        const height = bfs(i);
        if (height < minHeight) {
            minHeight = height;
            res.length = 0;
            res.push(i);
        } else if (height === minHeight) {
            res.push(i);
        }
    }
    return res;
}

// Driver code
console.log(findMinHeightTrees(4, [[1,0],[1,2],[1,3]])); // Output: [1]
Line Notes
if (n === 1) return [0];Handle single node tree quickly
const graph = Array.from({length: n}, () => []);Build adjacency list for graph
const queue = [[root, 0]];Initialize BFS queue with root and depth 0
maxDepth = Math.max(maxDepth, depth);Track maximum depth during BFS
for (let i = 0; i < n; i++) {Try each node as root to find minimal height
const height = bfs(i);Compute height for current root candidate
if (height < minHeight) {Update minimum height and result list
⏱
Complexity
TimeO(n^2) because for each node we do a BFS/DFS that takes O(n) time
SpaceO(n) for adjacency list and BFS queue

We run BFS from each node, each BFS takes O(n), repeated n times leads to O(n^2).

💡 For n=20, this means roughly 400 BFS traversals, which is manageable but grows quickly and becomes impractical for large n.
Interview Verdict: TLE for large inputs

This approach is too slow for large trees but is useful to understand the problem and correctness before optimizing.

🧠
Better Approach (Leaf Trimming Using BFS)
💡 This approach improves efficiency by iteratively removing leaves, which are nodes with only one connection, to find the centroids of the tree.

Intuition

Repeatedly remove leaves layer by layer until 1 or 2 nodes remain. These remaining nodes are the roots of Minimum Height Trees.

Algorithm

  1. Build the adjacency list and compute the degree of each node.
  2. Initialize a queue with all leaf nodes (degree == 1).
  3. While more than 2 nodes remain:
  4. Remove current leaves and reduce the degree of their neighbors.
  5. Add new leaves to the queue.
  6. Return the remaining 1 or 2 nodes.
💡 The key insight is that centroids are the last nodes remaining after removing leaves repeatedly.
</>
Code
from collections import deque

def findMinHeightTrees(n, edges):
    if n == 1:
        return [0]

    graph = [[] for _ in range(n)]
    degree = [0] * n
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)
        degree[u] += 1
        degree[v] += 1

    leaves = deque([i for i in range(n) if degree[i] == 1])
    remaining_nodes = n

    while remaining_nodes > 2:
        leaves_count = len(leaves)
        remaining_nodes -= leaves_count
        for _ in range(leaves_count):
            leaf = leaves.popleft()
            for nei in graph[leaf]:
                degree[nei] -= 1
                if degree[nei] == 1:
                    leaves.append(nei)
    return list(leaves)

# Driver code
if __name__ == '__main__':
    print(findMinHeightTrees(6, [[0,3],[1,3],[2,3],[4,3],[5,4]]))  # Output: [3,4]
Line Notes
degree = [0] * nTrack number of edges connected to each node
leaves = deque([i for i in range(n) if degree[i] == 1])Initialize queue with all leaves
while remaining_nodes > 2:Continue trimming until 1 or 2 nodes remain
degree[nei] -= 1Reduce degree of neighbors as leaves are removed
if degree[nei] == 1:New leaves are added to the queue
import java.util.*;

public class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        if (n == 1) return Collections.singletonList(0);

        List<List<Integer>> graph = new ArrayList<>();
        int[] degree = new int[n];
        for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
        for (int[] edge : edges) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
            degree[edge[0]]++;
            degree[edge[1]]++;
        }

        Queue<Integer> leaves = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            if (degree[i] == 1) leaves.offer(i);
        }

        int remainingNodes = n;
        while (remainingNodes > 2) {
            int leavesCount = leaves.size();
            remainingNodes -= leavesCount;
            for (int i = 0; i < leavesCount; i++) {
                int leaf = leaves.poll();
                for (int nei : graph.get(leaf)) {
                    degree[nei]--;
                    if (degree[nei] == 1) leaves.offer(nei);
                }
            }
        }
        return new ArrayList<>(leaves);
    }

    // Driver code
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.findMinHeightTrees(6, new int[][]{{0,3},{1,3},{2,3},{4,3},{5,4}})); // [3,4]
    }
}
Line Notes
int[] degree = new int[n];Array to store degree of each node
if (degree[i] == 1) leaves.offer(i);Identify initial leaves
while (remainingNodes > 2) {Trim leaves until 1 or 2 nodes remain
degree[nei]--;Decrease degree of neighbors as leaves are removed
if (degree[nei] == 1) leaves.offer(nei);Add new leaves to queue
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

class Solution {
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if (n == 1) return {0};

        vector<vector<int>> graph(n);
        vector<int> degree(n, 0);
        for (auto& e : edges) {
            graph[e[0]].push_back(e[1]);
            graph[e[1]].push_back(e[0]);
            degree[e[0]]++;
            degree[e[1]]++;
        }

        queue<int> leaves;
        for (int i = 0; i < n; i++) {
            if (degree[i] == 1) leaves.push(i);
        }

        int remainingNodes = n;
        while (remainingNodes > 2) {
            int leavesCount = leaves.size();
            remainingNodes -= leavesCount;
            for (int i = 0; i < leavesCount; i++) {
                int leaf = leaves.front(); leaves.pop();
                for (int nei : graph[leaf]) {
                    degree[nei]--;
                    if (degree[nei] == 1) leaves.push(nei);
                }
            }
        }

        vector<int> res;
        while (!leaves.empty()) {
            res.push_back(leaves.front());
            leaves.pop();
        }
        return res;
    }
};

// Driver code
int main() {
    Solution sol;
    vector<vector<int>> edges = {{0,3},{1,3},{2,3},{4,3},{5,4}};
    vector<int> res = sol.findMinHeightTrees(6, edges);
    for (int r : res) cout << r << ' '; // Output: 3 4
    cout << endl;
    return 0;
}
Line Notes
vector<int> degree(n, 0);Track degree of each node
if (degree[i] == 1) leaves.push(i);Initialize leaves queue
while (remainingNodes > 2) {Trim leaves until 1 or 2 nodes remain
degree[nei]--;Decrease degree of neighbors after removing leaves
if (degree[nei] == 1) leaves.push(nei);Add new leaves to queue
function findMinHeightTrees(n, edges) {
    if (n === 1) return [0];

    const graph = Array.from({length: n}, () => []);
    const degree = new Array(n).fill(0);
    for (const [u, v] of edges) {
        graph[u].push(v);
        graph[v].push(u);
        degree[u]++;
        degree[v]++;
    }

    const leaves = [];
    for (let i = 0; i < n; i++) {
        if (degree[i] === 1) leaves.push(i);
    }

    let remainingNodes = n;
    while (remainingNodes > 2) {
        const leavesCount = leaves.length;
        remainingNodes -= leavesCount;
        const newLeaves = [];
        for (let i = 0; i < leavesCount; i++) {
            const leaf = leaves[i];
            for (const nei of graph[leaf]) {
                degree[nei]--;
                if (degree[nei] === 1) newLeaves.push(nei);
            }
        }
        leaves.length = 0;
        Array.prototype.push.apply(leaves, newLeaves);
    }
    return leaves;
}

// Driver code
console.log(findMinHeightTrees(6, [[0,3],[1,3],[2,3],[4,3],[5,4]])); // Output: [3,4]
Line Notes
const degree = new Array(n).fill(0);Track degree of each node
if (degree[i] === 1) leaves.push(i);Initialize leaves array
while (remainingNodes > 2) {Trim leaves until 1 or 2 nodes remain
degree[nei]--;Decrease degree of neighbors after removing leaves
if (degree[nei] === 1) newLeaves.push(nei);Add new leaves to next layer
⏱
Complexity
TimeO(n) because each edge and node is processed at most once during leaf trimming
SpaceO(n) for adjacency list, degree array, and queue

Each node is added and removed from the queue at most once, so total work is linear.

💡 For n=10^5, this approach is efficient and scalable, unlike brute force.
Interview Verdict: Accepted and optimal for large inputs

This approach is the standard solution for this problem and is expected in interviews.

🧠
Optimal Approach (Same as Better but with Early Exit and Edge Cases Handling)
💡 This approach is a refined version of leaf trimming with careful handling of edge cases and early returns to improve clarity and robustness.

Intuition

Use the leaf trimming method but add checks for trivial cases and return early when possible to avoid unnecessary computation.

Algorithm

  1. If n == 1, return [0] immediately.
  2. Build adjacency list and degree array.
  3. Initialize leaves queue with nodes of degree 1.
  4. While more than 2 nodes remain, remove leaves and update neighbors.
  5. Return the remaining nodes as centroids.
💡 Early exit and edge case handling make the solution robust and clean.
</>
Code
from collections import deque

def findMinHeightTrees(n, edges):
    if n == 1:
        return [0]

    graph = [[] for _ in range(n)]
    degree = [0] * n
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)
        degree[u] += 1
        degree[v] += 1

    leaves = deque([i for i in range(n) if degree[i] == 1])
    remaining_nodes = n

    while remaining_nodes > 2:
        leaves_count = len(leaves)
        remaining_nodes -= leaves_count
        for _ in range(leaves_count):
            leaf = leaves.popleft()
            for nei in graph[leaf]:
                degree[nei] -= 1
                if degree[nei] == 1:
                    leaves.append(nei)
    return list(leaves)

# Driver code
if __name__ == '__main__':
    print(findMinHeightTrees(1, []))  # Output: [0]
Line Notes
if n == 1:Handle single node tree immediately
degree = [0] * nInitialize degree array
leaves = deque([i for i in range(n) if degree[i] == 1])Find initial leaves
while remaining_nodes > 2:Trim leaves until 1 or 2 nodes remain
if degree[nei] == 1:Add new leaves to queue
import java.util.*;

public class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        if (n == 1) return Collections.singletonList(0);

        List<List<Integer>> graph = new ArrayList<>();
        int[] degree = new int[n];
        for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
        for (int[] edge : edges) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
            degree[edge[0]]++;
            degree[edge[1]]++;
        }

        Queue<Integer> leaves = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            if (degree[i] == 1) leaves.offer(i);
        }

        int remainingNodes = n;
        while (remainingNodes > 2) {
            int leavesCount = leaves.size();
            remainingNodes -= leavesCount;
            for (int i = 0; i < leavesCount; i++) {
                int leaf = leaves.poll();
                for (int nei : graph.get(leaf)) {
                    degree[nei]--;
                    if (degree[nei] == 1) leaves.offer(nei);
                }
            }
        }
        return new ArrayList<>(leaves);
    }

    // Driver code
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.findMinHeightTrees(1, new int[][]{})); // [0]
    }
}
Line Notes
if (n == 1) return Collections.singletonList(0);Early return for single node tree
int[] degree = new int[n];Degree array initialization
if (degree[i] == 1) leaves.offer(i);Identify initial leaves
while (remainingNodes > 2) {Trim leaves until 1 or 2 nodes remain
if (degree[nei] == 1) leaves.offer(nei);Add new leaves to queue
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

class Solution {
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if (n == 1) return {0};

        vector<vector<int>> graph(n);
        vector<int> degree(n, 0);
        for (auto& e : edges) {
            graph[e[0]].push_back(e[1]);
            graph[e[1]].push_back(e[0]);
            degree[e[0]]++;
            degree[e[1]]++;
        }

        queue<int> leaves;
        for (int i = 0; i < n; i++) {
            if (degree[i] == 1) leaves.push(i);
        }

        int remainingNodes = n;
        while (remainingNodes > 2) {
            int leavesCount = leaves.size();
            remainingNodes -= leavesCount;
            for (int i = 0; i < leavesCount; i++) {
                int leaf = leaves.front(); leaves.pop();
                for (int nei : graph[leaf]) {
                    degree[nei]--;
                    if (degree[nei] == 1) leaves.push(nei);
                }
            }
        }

        vector<int> res;
        while (!leaves.empty()) {
            res.push_back(leaves.front());
            leaves.pop();
        }
        return res;
    }
};

// Driver code
int main() {
    Solution sol;
    vector<vector<int>> edges = {};
    vector<int> res = sol.findMinHeightTrees(1, edges);
    for (int r : res) cout << r << ' '; // Output: 0
    cout << endl;
    return 0;
}
Line Notes
if (n == 1) return {0};Handle trivial single-node tree quickly
vector<int> degree(n, 0);Initialize degree array
if (degree[i] == 1) leaves.push(i);Find initial leaves
while (remainingNodes > 2) {Trim leaves until 1 or 2 nodes remain
if (degree[nei] == 1) leaves.push(nei);Add new leaves to queue
function findMinHeightTrees(n, edges) {
    if (n === 1) return [0];

    const graph = Array.from({length: n}, () => []);
    const degree = new Array(n).fill(0);
    for (const [u, v] of edges) {
        graph[u].push(v);
        graph[v].push(u);
        degree[u]++;
        degree[v]++;
    }

    let leaves = [];
    for (let i = 0; i < n; i++) {
        if (degree[i] === 1) leaves.push(i);
    }

    let remainingNodes = n;
    while (remainingNodes > 2) {
        const leavesCount = leaves.length;
        remainingNodes -= leavesCount;
        const newLeaves = [];
        for (let i = 0; i < leavesCount; i++) {
            const leaf = leaves[i];
            for (const nei of graph[leaf]) {
                degree[nei]--;
                if (degree[nei] === 1) newLeaves.push(nei);
            }
        }
        leaves = newLeaves;
    }
    return leaves;
}

// Driver code
console.log(findMinHeightTrees(1, [])); // Output: [0]
Line Notes
if (n === 1) return [0];Early return for single node tree
const degree = new Array(n).fill(0);Initialize degree array
if (degree[i] === 1) leaves.push(i);Identify initial leaves
while (remainingNodes > 2) {Trim leaves until 1 or 2 nodes remain
if (degree[nei] === 1) newLeaves.push(nei);Add new leaves to next layer
⏱
Complexity
TimeO(n) same as previous approach
SpaceO(n)

Early exit does not change asymptotic complexity but improves runtime for trivial cases.

💡 This approach is clean and handles all edge cases efficiently.
Interview Verdict: Accepted and recommended for interviews

Shows attention to detail and robustness, which interviewers appreciate.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, always code the leaf trimming approach (Approach 2 or 3) because it is efficient and accepted.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(n^2)O(n)NoN/AMention only - never code due to inefficiency
2. Better (Leaf Trimming BFS)O(n)O(n)NoN/ACode this approach for optimal solution
3. Optimal (Leaf Trimming with Early Exit)O(n)O(n)NoN/ACode this for clean and robust solution
💼
Interview Strategy
💡 Use this guide to understand the problem deeply, practice coding the leaf trimming approach, and prepare to explain why brute force is inefficient and how pruning leads to optimal solutions.

How to Present

Step 1: Clarify the problem and constraints.Step 2: Describe the brute force approach to show understanding.Step 3: Explain why brute force is inefficient.Step 4: Introduce the leaf trimming approach and its intuition.Step 5: Code the leaf trimming solution carefully.Step 6: Test with edge cases and explain complexity.

Time Allocation

Clarify: 3min → Approach: 5min → Code: 10min → Test: 5min. Total ~23min

What the Interviewer Tests

Understanding of graph/tree properties, ability to optimize naive solutions, and knowledge of BFS and topological concepts.

Common Follow-ups

  • What if the graph is not a tree? → Need cycle detection and handle disconnected components.
  • Can you find the height of the tree from the MHT roots? → BFS from root to find max depth.
💡 These follow-ups test your ability to generalize and extend the solution beyond the base problem.
🔍
Pattern Recognition

When to Use

1) Input is a tree or acyclic graph, 2) Asked to minimize height or distance, 3) Need to find central nodes, 4) Problem hints at pruning or BFS layers.

Signature Phrases

minimum height treesremove leaves iterativelyfind centroids of a tree

NOT This Pattern When

Problems that require shortest path or dynamic programming on trees but do not involve iterative leaf removal.

Similar Problems

Find the Town Judge - uses graph degrees and pruningCourse Schedule - uses topological sort and in-degreeNumber of Connected Components - uses BFS/DFS on graphs

Practice

(1/5)
1. Given the following Python code snippet implementing the optimal alien dictionary solution, what is the final returned order for the input words = ["wrt", "wrf", "er", "ett", "rftt"]?
from collections import defaultdict, deque

def alienOrder(words):
    graph = defaultdict(set)
    in_degree = {c: 0 for word in words for c in word}

    for i in range(len(words) - 1):
        w1, w2 = words[i], words[i + 1]
        min_len = min(len(w1), len(w2))
        if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:
            return ""
        for j in range(min_len):
            if w1[j] != w2[j]:
                if w2[j] not in graph[w1[j]]:
                    graph[w1[j]].add(w2[j])
                    in_degree[w2[j]] += 1
                break

    queue = deque([c for c in in_degree if in_degree[c] == 0])
    order = []
    while queue:
        c = queue.popleft()
        order.append(c)
        for nei in graph[c]:
            in_degree[nei] -= 1
            if in_degree[nei] == 0:
                queue.append(nei)
    if len(order) < len(in_degree):
        return ""
    return "".join(order)

print(alienOrder(words))
easy
A. "wretf"
B. "wertf"
C. "wert"
D. "wrtef"

Solution

  1. Step 1: Build graph and in-degree map

    Edges: w->e, e->r, r->t, t->f. In-degree counts: w=0, e=1, r=1, t=1, f=1.
  2. Step 2: Perform BFS topological sort

    Start with 'w' (in-degree 0), then 'e', 'r', 't', 'f' in order as their in-degree drops to zero.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Order "wertf" respects all precedence edges [OK]
Hint: Trace BFS queue and in-degree updates [OK]
Common Mistakes:
  • Stopping early and missing characters
  • Misordering characters with same in-degree
  • Ignoring prefix invalid case
2. What is the time complexity of Kahn's Algorithm for finding a topological order in a graph with V vertices and E edges?
medium
A. O(V * E) because each vertex processes all edges repeatedly.
B. O(V^2) because in-degree updates require scanning all vertices.
C. O(V + E) because each vertex and edge is processed exactly once.
D. O(E log V) due to priority queue operations for ordering.

Solution

  1. Step 1: Identify operations on vertices and edges

    Each vertex is enqueued and dequeued once; each edge is visited once to decrement in-degree.
  2. Step 2: Sum total work

    Processing all vertices and edges once leads to O(V + E) time complexity.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Linear time in graph size is standard for BFS topological sort [OK]
Hint: Each edge and vertex processed once -> O(V + E) [OK]
Common Mistakes:
  • Assuming nested loops cause O(V*E)
  • Confusing in-degree updates as scanning all vertices
  • Thinking priority queues are used by default
3. Suppose the alien dictionary problem is extended so that characters can appear multiple times in the same word and the order must reflect the first occurrence precedence only. Which modification to the topological sort approach correctly handles this variant without breaking correctness or efficiency?
hard
A. Modify graph construction to add edges for every differing character pair in all positions, not just first differing character
B. Use a brute force approach comparing all pairs of words and all character positions to build edges
C. Keep only the first differing character pair between adjacent words to add edges, ignoring subsequent differences
D. Ignore repeated characters and treat each character as unique node per occurrence

Solution

  1. Step 1: Understand variant requirement

    Order depends only on first differing character between adjacent words, even if characters repeat later.
  2. Step 2: Confirm correct approach

    Adding edges only for the first differing character pair preserves partial order and avoids cycles or redundant edges.
  3. Step 3: Reject incorrect modifications

    Adding edges for all differing pairs or treating repeated chars as unique nodes breaks correctness or efficiency.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    First differing character edge addition is minimal and sufficient [OK]
Hint: Only first differing character pair defines order [OK]
Common Mistakes:
  • Adding edges for all differing pairs causing cycles
  • Using brute force all pairs approach losing efficiency
  • Treating repeated chars as distinct nodes
4. Suppose the problem is modified so that some courses can be taken multiple times (repeatedly) and prerequisites must be satisfied each time before retaking. Which of the following statements about adapting the BFS topological sort approach is correct?
hard
A. We should switch to a dynamic programming approach because BFS topological sort cannot handle repeated courses.
B. The original BFS topological sort can be used without changes since it handles cycles by returning -1.
C. We can use BFS topological sort by resetting indegrees after each semester and reprocessing courses as needed.
D. The problem becomes invalid because repeated courses create cycles; BFS topological sort cannot be applied directly.

Solution

  1. Step 1: Understand repeated courses and prerequisites

    Allowing repeated courses with prerequisites means cycles can form because a course depends on itself indirectly.
  2. Step 2: Analyze BFS topological sort applicability

    BFS topological sort requires a DAG; cycles break this assumption, so it cannot be applied directly.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Repeated courses induce cycles -> BFS topological sort invalid [OK]
Hint: Repeated courses create cycles, breaking DAG assumption [OK]
Common Mistakes:
  • Assuming BFS topological sort handles cycles by returning -1
  • Thinking resetting indegrees can simulate repeats correctly
  • Believing DP is always needed without analyzing graph structure
5. Suppose the problem is modified so that elements in the sequences can be reused multiple times (i.e., sequences may contain repeated elements). Which of the following changes to the DFS postorder topological sort approach is necessary to correctly determine if the original sequence can be uniquely reconstructed?
hard
A. No changes needed; the existing DFS approach handles repeated elements naturally.
B. Modify graph construction to allow multiple edges between the same nodes and adjust cycle detection accordingly.
C. Switch to BFS topological sort with a queue that tracks in-degree counts considering repeated edges.
D. Preprocess sequences to remove duplicates before building the graph, then run the original DFS approach.

Solution

  1. Step 1: Understand impact of repeated elements

    Repeated elements mean multiple edges between same nodes may exist, affecting graph structure and cycle detection.
  2. Step 2: Adjust graph and DFS accordingly

    Graph must allow multiple edges or count edge multiplicity; cycle detection must consider these to avoid false negatives or infinite recursion.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Handling repeated edges is essential for correctness with reused elements [OK]
Hint: Repeated elements require multi-edge aware graph and cycle detection [OK]
Common Mistakes:
  • Assuming no changes needed
  • Removing duplicates loses info
  • Switching to BFS without handling repeats
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard