The diameter of a binary tree is the longest path (in edges) between any two nodes — it need not pass through the root. A single post-order DFScomputes every node's height and, as a by-product, updates a best counter with leftEdges + rightEdges at every node.
Given the root of a binary tree, return the length of the diameter — the number of edges on the longest path between any two nodes. The path may or may not pass through the root.
Example: 1,2,3,4,5: the tree looks like
1
/ \
2 3
/ \
4 5The longest path is 4 → 2 → 1 → 3 or 5 → 2 → 1 → 3, both with 3 edges. Answer: 3.leftEdges + rightEdges. Run a post-order DFS that returns height upward but updates a global best sideways. One pass, no extra data structure.null node has height -1(using the "-1 for null" convention makes the edge-count arithmetic clean: a leaf has height 0, so a single edge to it is 0 + 1 = 1).leftEdges = node.left !== null ? leftH + 1 : 0. A null child contributes 0 edges, not -1 + 1 = 0 (same math, but explicit is safer).best = Math.max(best, leftEdges + rightEdges). This captures the diameter through every possible "turning point" in the tree.return Math.max(leftH, rightH) + 1. This is what the parent node needs to compute its own edge counts.height upward while updating bestas a side-effect feels wrong — the function seems to do two things at once. It's fine: the return value serves the parent; the side-effect serves the global answer. These are two different questions. The key discipline is never return the candidate diameter — always return height.The recursion stack is O(h) where h is the tree height — O(log n) for a balanced tree, O(n) in the worst case (a skewed chain). An iterative post-order with an explicit stack achieves the same time at O(n) space, but the recursive form is cleaner to read and usually fine in interviews.
1▶function diameterOfBinaryTree(root: TreeNode | null): number {2▶ let best = 0;34▶ function height(node: TreeNode | null): number {5 if (node === null) return -1; // null child contributes -1 height67 const leftH = height(node.left);8 const rightH = height(node.right);910 // edges to left/right subtrees (0 when child is absent)11 const leftEdges = node.left !== null ? leftH + 1 : 0;12 const rightEdges = node.right !== null ? rightH + 1 : 0;1314 best = Math.max(best, leftEdges + rightEdges);1516 return Math.max(leftH, rightH) + 1; // height of this node17 }1819 height(root);20 return best;21}
function diameterOfBinaryTree(root: TreeNode | null): number {
let best = 0;
function height(node: TreeNode | null): number {
if (node === null) return -1; // null child contributes -1 height
const leftH = height(node.left);
const rightH = height(node.right);
// edges to left/right subtrees (0 when child is absent)
const leftEdges = node.left !== null ? leftH + 1 : 0;
const rightEdges = node.right !== null ? rightH + 1 : 0;
best = Math.max(best, leftEdges + rightEdges);
return Math.max(leftH, rightH) + 1; // height of this node
}
height(root);
return best;
}bestis declared in the outer scope so the inner recursive function can update it without passing it around. This is the standard pattern for a "global best" in tree DFS.null returns -1. Using -1 (not 0) means a leaf node returns Math.max(-1,-1)+1 = 0, and the edge to that leaf is 0+1 = 1. It keeps the arithmetic consistent without any special-casing.leftEdges is leftH + 1 if a left child exists, else 0. Null children contribute 0 edges to the path, not a negative number — the explicit guard prevents accidental -1+1=0 bugs when the convention is later changed.leftEdges + rightEdges. Taking the max over every node in the tree finds the overall diameter, even when it passes through a non-root node.Math.max(leftH, rightH) + 1 picks the taller child and adds 1 for the edge connecting this node to it.best = Math.max(best, maxLeftCost + maxRightCost).best = Math.max(best, node.val + leftGain + rightGain).function diameterOfBinaryTree(root: TreeNode | null): number {
let best = 0;
function height(node: TreeNode | null): number {
if (node === null) return -1; // null child contributes -1 height
const leftH = height(node.left);
const rightH = height(node.right);
// edges to left/right subtrees (0 when child is absent)
const leftEdges = node.left !== null ? leftH + 1 : 0;
const rightEdges = node.right !== null ? rightH + 1 : 0;
best = Math.max(best, leftEdges + rightEdges);
return Math.max(leftH, rightH) + 1; // height of this node
}
height(root);
return best;
}For each node, the diameter through it is height(left) + height(right) + 2. Call a separate heighthelper at every node and take the max — the literal definition, no shared state.
function diameterOfBinaryTree(root: TreeNode | null): number {
// Plain height: longest downward path in edges (null = -1, leaf = 0).
function height(node: TreeNode | null): number {
if (node === null) return -1;
return Math.max(height(node.left), height(node.right)) + 1;
}
let best = 0;
function visit(node: TreeNode | null): void {
if (node === null) return;
// Diameter through this node = edges down-left + edges down-right.
const through = height(node.left) + height(node.right) + 2;
best = Math.max(best, through);
visit(node.left);
visit(node.right);
}
visit(root);
return best;
}height, which itself walks that node's whole subtree — so subtree heights are recomputed over and over, giving O(n²) on a skewed tree. The post-order solution returns the height while updating best, collapsing both jobs into a single O(n) pass.| "longest path between any two nodes" | post-order DFS, leftEdges+rightEdges |
| "tree path not through root" | global best updated at each node |
| "return height, update global" | the height-with-side-effect pattern |
| "Binary Tree Max Path Sum" | same skeleton, return gain not sum |
let best = 0;
function height(node: TreeNode | null): number {
if (node === null) return -1;
const leftH = height(node.left);
const rightH = height(node.right);
const leftEdges = node.left !== null ? leftH + 1 : 0;
const rightEdges = node.right !== null ? rightH + 1 : 0;
best = Math.max(best, leftEdges + rightEdges);
return Math.max(leftH, rightH) + 1;
}
height(root);
return best;heightupward (for the parent's edge calculation). Updates best sideways (the diameter through this node).1,2,3,4,5 (the worked example), what is the diameter?null node returns -1 from the height function. Why not 0?