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;
};
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.
edges
, not an adjacency list or matrix.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.
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:
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.
u
.u
u
, perform another BFS/DFS to find the farthest node from u
. The distance to this node is the diameter of the tree.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:
Example Input: edges = [[0,1], [1,2], [1,3], [3,4], [4,5]]
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.
Why? Each node and edge is processed a constant number of times, and we only store the graph and a visited set.
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.