Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1245. Tree Diameter - Leetcode Solution

Code Implementation

class Solution:
    def treeDiameter(self, edges):
        from collections import defaultdict, deque

        # Build the adjacency list
        graph = defaultdict(list)
        for u, v in edges:
            graph[u].append(v)
            graph[v].append(u)

        # Helper: BFS to find the farthest node and its distance from a start node
        def bfs(start):
            visited = set()
            queue = deque([(start, 0)])
            visited.add(start)
            farthest_node = start
            max_dist = 0
            while queue:
                node, dist = queue.popleft()
                if dist > max_dist:
                    max_dist = dist
                    farthest_node = node
                for neighbor in graph[node]:
                    if neighbor not in visited:
                        visited.add(neighbor)
                        queue.append((neighbor, dist + 1))
            return farthest_node, max_dist

        # 1st BFS from any node (say 0) to find the farthest node
        u, _ = bfs(0)
        # 2nd BFS from u to find the farthest node from u (this is the diameter)
        v, diameter = bfs(u)
        return diameter
      
class Solution {
public:
    int treeDiameter(vector<vector<int>>& edges) {
        int n = edges.size() + 1;
        vector<vector<int>> graph(n);
        for (auto& e : edges) {
            graph[e[0]].push_back(e[1]);
            graph[e[1]].push_back(e[0]);
        }
        
        // Helper: BFS to find farthest node and distance
        auto bfs = [&](int start) {
            vector<bool> visited(n, false);
            queue<pair<int, int>> q;
            q.push({start, 0});
            visited[start] = true;
            int far_node = start, max_dist = 0;
            while (!q.empty()) {
                auto [node, dist] = q.front(); q.pop();
                if (dist > max_dist) {
                    max_dist = dist;
                    far_node = node;
                }
                for (int nei : graph[node]) {
                    if (!visited[nei]) {
                        visited[nei] = true;
                        q.push({nei, dist + 1});
                    }
                }
            }
            return make_pair(far_node, max_dist);
        };
        
        auto [u, _] = bfs(0);
        auto [v, diameter] = bfs(u);
        return diameter;
    }
};
      
import java.util.*;

class Solution {
    public int treeDiameter(int[][] edges) {
        int n = edges.length + 1;
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
        for (int[] e : edges) {
            graph.get(e[0]).add(e[1]);
            graph.get(e[1]).add(e[0]);
        }
        // Helper: BFS to find farthest node and distance
        class Pair {
            int node, dist;
            Pair(int n, int d) { node = n; dist = d; }
        }
        Pair bfs(int start) {
            boolean[] visited = new boolean[n];
            Queue<Pair> queue = new LinkedList<>();
            queue.offer(new Pair(start, 0));
            visited[start] = true;
            int farNode = start, maxDist = 0;
            while (!queue.isEmpty()) {
                Pair p = queue.poll();
                if (p.dist > maxDist) {
                    maxDist = p.dist;
                    farNode = p.node;
                }
                for (int nei : graph.get(p.node)) {
                    if (!visited[nei]) {
                        visited[nei] = true;
                        queue.offer(new Pair(nei, p.dist + 1));
                    }
                }
            }
            return new Pair(farNode, maxDist);
        }
        Pair u = bfs(0);
        Pair v = bfs(u.node);
        return v.dist;
    }
}
      
var treeDiameter = function(edges) {
    // Build adjacency list
    const graph = {};
    for (const [u, v] of edges) {
        if (!(u in graph)) graph[u] = [];
        if (!(v in graph)) graph[v] = [];
        graph[u].push(v);
        graph[v].push(u);
    }

    // Helper: BFS to find farthest node and its distance
    function bfs(start) {
        const visited = new Set();
        let queue = [[start, 0]];
        visited.add(start);
        let farthest = start, maxDist = 0;
        while (queue.length) {
            const [node, dist] = queue.shift();
            if (dist > maxDist) {
                maxDist = dist;
                farthest = node;
            }
            for (const nei of graph[node]) {
                if (!visited.has(nei)) {
                    visited.add(nei);
                    queue.push([nei, dist + 1]);
                }
            }
        }
        return [farthest, maxDist];
    }

    // 1st BFS from any node (say 0)
    const [u, _] = bfs(0);
    // 2nd BFS from u to find diameter
    const [v, diameter] = bfs(u);
    return diameter;
};
      

Problem Description

You are given an undirected tree represented as a list of edges, where each edge is a pair of integer nodes [u, v]. The tree has no cycles and is connected. Your task is to determine the diameter of the tree, which is defined as the number of edges on the longest path between any two nodes in the tree.

  • Each edge connects two different nodes.
  • There is exactly one simple path between any two nodes.
  • The input is a list of edges, not an adjacency list or matrix.
  • The tree can have up to 10^4 nodes.

Key constraints: The tree is always valid and connected. The answer should be the length (in edges) of the longest path, and nodes/edges cannot be reused along a single path.

Thought Process

To solve this problem, we need to find the longest path between any two nodes in a tree. At first glance, it might seem like we need to check all possible pairs of nodes and compute the path between them, but that would be extremely inefficient for large trees.

Instead, we can leverage some key properties of trees:

  • There is exactly one path between any two nodes (no cycles).
  • The longest path in a tree (the diameter) always goes between two leaf nodes.

A brute-force approach would involve running a search from every node, but this would be too slow. By thinking about the structure of a tree, we realize that if we pick any node and find the farthest node from it using Breadth-First Search (BFS) or Depth-First Search (DFS), and then repeat the process from this farthest node, the distance to the next farthest node is the diameter. This is a classic property of trees.

This insight allows us to avoid checking all pairs and instead use two BFS (or DFS) traversals for an efficient solution.

Solution Approach

  • Step 1: Build the Graph
    • We receive the edges as input, so we first construct an adjacency list to represent the tree for easy traversal.
  • Step 2: First BFS/DFS to Find a Far Node
    • Pick any node (commonly node 0) and perform BFS (or DFS) to find the farthest node from it. Let's call this node u.
  • Step 3: Second BFS/DFS from u
    • From node u, perform another BFS/DFS to find the farthest node from u. The distance to this node is the diameter of the tree.
  • Step 4: Return the Diameter
    • The result is the length (in edges) of the longest path found in Step 3.

Why does this work? In a tree, the path between two farthest nodes is always the diameter. The first BFS/DFS finds one end of the diameter, and the second finds the other end and computes the distance.

Design Choices:

  • We use an adjacency list for efficient neighbor lookups.
  • BFS is chosen for its simplicity and guaranteed shortest path discovery in unweighted graphs.
  • Only two traversals are needed, making the approach efficient even for large trees.

Example Walkthrough

Example Input: edges = [[0,1], [1,2], [1,3], [3,4], [4,5]]

  1. Build the adjacency list:
    • 0: [1]
    • 1: [0, 2, 3]
    • 2: [1]
    • 3: [1, 4]
    • 4: [3, 5]
    • 5: [4]
  2. First BFS from node 0:
    • Farthest node found is node 5 at distance 4 (path: 0-1-3-4-5)
  3. Second BFS from node 5:
    • Farthest node found is node 2 at distance 5 (path: 5-4-3-1-0-2)
  4. Diameter: 5 (edges)

Explanation: The longest path in this tree is from node 2 to node 5, passing through nodes 1, 0, 3, and 4, which covers 5 edges.

Time and Space Complexity

  • Brute-Force Approach:
    • For every node, perform BFS/DFS to every other node: O(N^2) time, where N is the number of nodes.
    • This is too slow for large trees (e.g. N = 10,000).
  • Optimized Approach (Two BFS/DFS):
    • Each BFS/DFS visits every node once: O(N) time per traversal.
    • We do two traversals, so total time is O(N).
    • Space complexity is O(N) for storing the adjacency list and visited nodes.

Why? Each node and edge is processed a constant number of times, and we only store the graph and a visited set.

Summary

The tree diameter problem can be solved efficiently using two BFS (or DFS) traversals. By leveraging the unique properties of trees, we avoid brute-force checks and instead use a clever two-pass strategy: first, find a far endpoint, then from there, find the diameter. This approach is elegant, intuitive, and scales well even for large trees.

The key insight is that the diameter is always between two leaf nodes, and this can be discovered with just two graph traversals. The algorithm is simple, fast, and easy to implement in any language.