Tree Diameter
Two BFS passes — first from any node finds a diameter endpoint; second BFS from there measures the diameter.
5 min read
tree bfs graph
Problem#
Given an undirected tree as an edge list, return its diameter — the number of edges on the longest path between any two nodes.
Examples#
edges = [[0,1],[0,2]]→2edges = [[0,1],[1,2],[2,3],[1,4],[4,5]]→4
Constraints#
0 <= edges.length < 10^4. Underlying graph is a tree.
Approach#
The classic two-BFS trick. From any node, BFS finds the farthest node u — guaranteed to be on a diameter. BFS from u to find the farthest node v; the distance is the diameter.
Solution#
class Solution {public: int treeDiameter(vector<vector<int>>& edges) { int n = edges.size() + 1; if (n == 1) return 0; vector<vector<int>> adj(n); for (auto& e : edges) { adj[e[0]].push_back(e[1]); adj[e[1]].push_back(e[0]); } auto bfs = [&](int src) { vector<int> dist(n, -1); queue<int> q; q.push(src); dist[src] = 0; int far = src; while (!q.empty()) { int u = q.front(); q.pop(); if (dist[u] > dist[far]) far = u; for (int v : adj[u]) { if (dist[v] == -1) { dist[v] = dist[u] + 1; q.push(v); } } } return make_pair(far, dist[far]); }; auto [u, d0] = bfs(0); (void)d0; auto [v, d] = bfs(u); (void)v; return d; }};func treeDiameter(edges [][]int) int { n := len(edges) + 1 if n == 1 { return 0 } adj := make([][]int, n) for _, e := range edges { adj[e[0]] = append(adj[e[0]], e[1]) adj[e[1]] = append(adj[e[1]], e[0]) } bfs := func(src int) (int, int) { dist := make([]int, n) for i := range dist { dist[i] = -1 } dist[src] = 0 q := []int{src} far := src for len(q) > 0 { u := q[0] q = q[1:] if dist[u] > dist[far] { far = u } for _, v := range adj[u] { if dist[v] == -1 { dist[v] = dist[u] + 1 q = append(q, v) } } } return far, dist[far] } u, _ := bfs(0) _, d := bfs(u) return d}from collections import dequefrom typing import List, Tuple
class Solution: def treeDiameter(self, edges: List[List[int]]) -> int: n = len(edges) + 1 if n == 1: return 0 adj: List[List[int]] = [[] for _ in range(n)] for a, b in edges: adj[a].append(b) adj[b].append(a)
def bfs(src: int) -> Tuple[int, int]: dist = [-1] * n dist[src] = 0 q = deque([src]) far = src while q: u = q.popleft() if dist[u] > dist[far]: far = u for v in adj[u]: if dist[v] == -1: dist[v] = dist[u] + 1 q.append(v) if dist[v] > dist[far]: far = v return far, dist[far]
u, _ = bfs(0) _, d = bfs(u) return dvar treeDiameter = function(edges) { const n = edges.length + 1; if (n === 1) return 0; const adj = Array.from({ length: n }, () => []); for (const [a, b] of edges) { adj[a].push(b); adj[b].push(a); } const bfs = (src) => { const dist = new Array(n).fill(-1); dist[src] = 0; const q = [src]; let head = 0, far = src; while (head < q.length) { const u = q[head++]; if (dist[u] > dist[far]) far = u; for (const v of adj[u]) { if (dist[v] === -1) { dist[v] = dist[u] + 1; q.push(v); } } } return [far, dist[far]]; }; const [u] = bfs(0); const [, d] = bfs(u); return d;};class Solution { private List<List<Integer>> adj; private int n;
public int treeDiameter(int[][] edges) { n = edges.length + 1; if (n == 1) return 0; adj = new ArrayList<>(); for (int i = 0; i < n; i++) adj.add(new ArrayList<>()); for (int[] e : edges) { adj.get(e[0]).add(e[1]); adj.get(e[1]).add(e[0]); } int[] first = bfs(0); int[] second = bfs(first[0]); return second[1]; }
private int[] bfs(int src) { int[] dist = new int[n]; Arrays.fill(dist, -1); dist[src] = 0; Deque<Integer> q = new ArrayDeque<>(); q.offer(src); int far = src; while (!q.isEmpty()) { int u = q.poll(); if (dist[u] > dist[far]) far = u; for (int v : adj.get(u)) { if (dist[v] == -1) { dist[v] = dist[u] + 1; q.offer(v); } } } return new int[]{far, dist[far]}; }}function treeDiameter(edges: number[][]): number { const n: number = edges.length + 1; if (n === 1) return 0; const adj: number[][] = Array.from({ length: n }, () => []); for (const [a, b] of edges) { adj[a].push(b); adj[b].push(a); } const bfs = (src: number): [number, number] => { const dist: number[] = new Array(n).fill(-1); dist[src] = 0; const q: number[] = [src]; let head: number = 0, far: number = src; while (head < q.length) { const u: number = q[head++]; if (dist[u] > dist[far]) far = u; for (const v of adj[u]) { if (dist[v] === -1) { dist[v] = dist[u] + 1; q.push(v); } } } return [far, dist[far]]; }; const [u] = bfs(0); const [, d] = bfs(u); return d;}Editorial#
The two-BFS property: in any tree, starting from an arbitrary node and walking to the farthest reachable node always lands on a diameter endpoint. A second BFS from there finds the other end and the diameter length. The proof is a swap argument on optimal paths.
Complexity#
- Time: O(V).
- Space: O(V).
Concept revision#
Related#