Hierarchical tree structure where each node has at most two children. The foundation for BST, Heap, and Trie.
Trees
Binary Tree Fundamentals
A hierarchical structure where each node has at most two children โ left and right. The conceptual foundation for BST, Heap, Trie, and every recursive divide-and-conquer algorithm on trees.
Hierarchical data structures unlock algorithmic patterns that linear structures cannot express โ trees encode parent-child relationships naturally, enabling divide-and-conquer solutions that split problems in half at every level and achieve O(log n) performance where arrays and lists require O(n). Each node in a binary tree holds a value and references to at most two children, called left and right, with one designated root node at the top and no cycles โ this constraint enables a recursive definition where every binary tree is either empty or a node with a left binary tree and a right binary tree. Every binary tree algorithm follows one universal pattern: do something at the current node, recurse into the left subtree, recurse into the right subtree โ the only variation is the order of these three steps, which produces the four traversal orders (preorder, inorder, postorder, level-order). The recursive structure means any operation you can perform on the whole tree works identically on any subtree, making trees the natural home for recursive thinking. Java's TreeMap and TreeSet use red-black trees internally, expression parsers build abstract syntax trees, and XML/JSON parsers represent hierarchical documents as trees โ understanding binary trees is prerequisite to understanding half of computer science.
Analogy: A corporate org chart where every manager has at most two direct reports. The CEO is the root โ no one manages the CEO. Every employee either has zero, one, or two direct reports. To find information about any employee, you start at the CEO and follow the reporting structure down โ each decision point either sends you left or right, cutting the remaining organisation in half at each step. That halving at each level is why balanced trees are so fast.
02
Section Two ยท Mental Model
How It Thinks
Each node has at most two children
โถ
Every problem splits into two independent subproblems โ divide and conquer is structurally enforced
Tree has no cycles โ each node has exactly one parent
โถ
Recursion terminates naturally โ null is the base case, every path to null is finite
Height h determines path length from root to leaf
โถ
Balanced tree: h = O(log n) โ operations proportional to height cost O(log n); unbalanced: h = O(n), same as a linked list
Every subtree is itself a valid binary tree
โถ
Recursive algorithms apply identically to the whole tree and any subtree โ no special cases for subtrees
Three positions relative to children: before, between, after
โถ
Three traversal orders โ preorder, inorder, postorder โ each revealing different structural information
No index, no hash โ only parent/child pointers
โถ
Search is O(n) without an ordering invariant โ the BST ordering constraint is what makes search O(log n)
03
Section Three ยท Terminology
Tree Terminology
Binary tree problems use a precise vocabulary that appears in every problem statement, every solution discussion, and every interview question โ knowing these terms cold prevents misunderstanding during problem analysis. The most commonly confused pairs are height vs depth (height measures from node down to farthest leaf; depth measures from root down to node) and full vs complete vs perfect (three distinct structural properties often incorrectly used interchangeably).
Tree Anatomy โ terminology reference
Terminology Reference
Term
Definition
Example in diagram
Root
The topmost node โ no parent
Node A
Leaf
Node with no children
H, I, E, F, G
Internal node
Node with at least one child
A, B, C, D
Parent
The node directly above
A is parent of B and C
Child
Direct descendant of a node
B and C are children of A
Sibling
Nodes sharing the same parent
D and E are siblings
Ancestor
Any node on path from root to node
A, B are ancestors of D
Descendant
Any node reachable going down
D, H, I are descendants of B
Edge
Connection between parent and child
AโB is an edge
Path
Sequence of edges between two nodes
AโBโDโH
Depth
Distance from root to a node
depth(D) = 2
Height of node
Max edges to a leaf in its subtree
height(B) = 2
Height of tree
Height of the root node
height(A) = 3
Level
Set of all nodes at same depth
Level 2 = {D, E, F, G}
Subtree
A node and all its descendants
Subtree rooted at B
Degree
Number of children of a node
degree(B) = 2, degree(H) = 0
Height vs depth โ the most confused tree terms:
Depth of a node counts edges from the root DOWN to that node โ the root has depth 0.
Height of a node counts edges from that node DOWN to the farthest leaf โ a leaf has height 0.
The height of a tree equals the height of its root.
These two measures run in opposite directions on the same tree, which is why they are so often confused.
04
Section Four ยท Tree Types
Tree Types and Structural Properties
Five structural properties define the tree types that appear in interviews and algorithms โ each is a constraint on the shape of the tree that enables specific operations or guarantees specific performance bounds. The most practically important are complete binary tree (enables array storage of heap) and balanced binary tree (guarantees O(log n) height for BST and AVL trees).
Five tree types โ structural properties
Comparison
Tree Type
Every node
Leaves
Height
Notes
Full
0 or 2 children
Any level
O(log n) to O(n)
Used in expression trees
Complete
Any
Deepest level, left-packed
O(log n)
Heap structure
Perfect
Exactly 2 (non-leaf)
All same level
Exactly log n
Theoretical ideal
Balanced
Any
Any level
O(log n)
AVL, Red-Black trees
Degenerate
Exactly 1 child
One leaf
O(n)
Sorted-insert BST pathology
Complete binary tree enables O(1) array storage:
A complete binary tree can be stored in an array with no pointers โ the root is at index 1, left child of node i is at 2i, right child at 2i+1, parent at i/2.
This is why heap (PriorityQueue) uses an array internally โ the complete tree property guarantees no gaps, so the array has no wasted space.
05
Section Five ยท Traversals
Tree Traversals
Inorder Traversal (Left โ Root โ Right)
Visits the left subtree, then the current node, then the right subtree โ produces sorted output on a BST.
Pseudocode
functioninorder(node):
if node isnull: returninorder(node.left) // recurse left firstvisit(node) // process current nodeinorder(node.right) // recurse right last// O(n) โ visits every node once
Inorder traversal โ visit order numbered 1โ7
Inorder = sorted output on a BST:
The inorder traversal of a BST always produces elements in ascending sorted order โ this is provable from the BST invariant.
The kth smallest element in a BST is found by inorder traversal stopping at the kth visited node.
Preorder Traversal (Root โ Left โ Right)
Visits the current node first, then the left subtree, then the right subtree โ used for tree copying and serialisation.
Pseudocode
functionpreorder(node):
if node isnull: returnvisit(node) // process current node FIRSTpreorder(node.left) // recurse leftpreorder(node.right) // recurse right// O(n)
Preorder traversal โ visit order numbered 1โ7
Preorder = tree serialisation format:
If you serialise a binary tree by writing nodes in preorder, you can reconstruct the original tree from the serialised form because the root always appears first.
This is the basis of LeetCode 297 (Serialize and Deserialize Binary Tree).
Postorder Traversal (Left โ Right โ Root)
Visits both subtrees completely before visiting the current node โ used when a node's value depends on its children.
Pseudocode
functionpostorder(node):
if node isnull: returnpostorder(node.left) // recurse leftpostorder(node.right) // recurse rightvisit(node) // process current node LAST// O(n)
Postorder traversal โ visit order numbered 1โ7
Postorder = process children before parent: Postorder is used when computing a node's value requires its children's values first โ tree height (need subtree heights before computing node height), directory size, and expression tree evaluation.
Level-Order Traversal (BFS)
Visits all nodes at depth 0, then depth 1, then depth 2 โ processes the tree level by level using a queue.
Pseudocode
functionlevelOrder(root):
if root isnull: return
queue = new Queue
enqueue(queue, root)
while queue is not empty:
node = dequeue(queue)
visit(node) // O(1) per nodeif node.left is not null: enqueue(queue, node.left)
if node.right is not null: enqueue(queue, node.right)
// O(n) total
Level-order traversal โ queue state at each step
Level-order = BFS = shortest path tree exploration:
Level-order traversal and BFS are the same algorithm โ both use a queue and both process nodes by distance from the start.
When a problem asks for "minimum depth" or "level-by-level processing", level-order is the answer.
Traversal Comparison
Traversal
Order
Use Case
Output on BST {8,3,10,1,6,9,14}
Inorder
Left โ Root โ Right
Sorted output, kth smallest
1, 3, 6, 8, 9, 10, 14
Preorder
Root โ Left โ Right
Serialisation, prefix expr
8, 3, 1, 6, 10, 9, 14
Postorder
Left โ Right โ Root
Height, size, deletion
1, 6, 3, 9, 14, 10, 8
Level-Order
Level by level
Min depth, level sums, BFS
8 | 3, 10 | 1, 6, 9, 14
Iterative Versions
Java โ Iterative Inorder and Preorder
// Iterative Inorder โ stack-based
List<Integer> inorderIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) { stack.push(curr); curr = curr.left; }
curr = stack.pop();
result.add(curr.val); // visit
curr = curr.right; // move right
}
return result;
}
// Iterative Preorder โ simpler: push right then left
List<Integer> preorderIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val); // visit before childrenif (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
return result;
}
Iterative inorder uses a two-phase loop โ understand it:
The iterative inorder pattern (go left until null pushing to stack, then pop, visit, go right) simulates the call stack of the recursive version.
Trace through 3 nodes by hand once โ it becomes intuitive.
06
Section Six ยท Operations
Core Operations
Height of Tree
Returns the number of edges on the longest path from the root to any leaf โ the base case is null returning -1 (or 0 if counting nodes rather than edges).
Pseudocode
functionheight(node):
if node isnull:
return -1 // -1 for edge-count definition
leftHeight = height(node.left)
rightHeight = height(node.right)
return 1 + max(leftHeight, rightHeight)
// O(n) โ visits every node
Height computation โ bottom-up with return values
Height is a postorder computation:
Height requires knowing the heights of both subtrees before computing the current node's height โ that is postorder: children first, then parent.
Any operation that aggregates information bottom-up (height, size, sum, balance check) is naturally postorder.
Count Nodes
Returns the total number of nodes in the tree โ the base case is null returning 0, and every node contributes 1.
Pseudocode
functioncountNodes(node):
if node isnull:
return 0 // base case โ empty treereturn 1 + countNodes(node.left) + countNodes(node.right)
// O(n) โ visits every node
Count, sum, and height all share the same skeleton:
These are the same postorder template with different combination functions.
Check if Balanced
Returns true if the absolute difference between the heights of the left and right subtrees is at most 1 for every node in the tree โ O(n) when done in one pass.
Pseudocode
functionisBalanced(node):
returncheckHeight(node) != -1 // -1 signals unbalancedfunctioncheckHeight(node):
if node isnull: return 0
leftH = checkHeight(node.left)
if leftH == -1: return -1 // propagate unbalanced signal
rightH = checkHeight(node.right)
if rightH == -1: return -1
if |leftH - rightH| > 1:
return -1 // this node is unbalancedreturn 1 + max(leftH, rightH) // O(n) โ one pass
Naive O(nยฒ) vs O(n) balance check:
The naive approach calls height() at every node โ O(n) per node ร O(n) nodes = O(nยฒ).
The single-pass approach uses -1 as a sentinel value that propagates upward when any subtree is unbalanced, short-circuiting the rest of the traversal.
Lowest Common Ancestor (LCA)
Finds the deepest node that is an ancestor of both p and q โ the meeting point when climbing from both nodes toward the root.
Pseudocode
functionlca(node, p, q):
if node isnull: returnnullif node == p or node == q:
return node // found one of the targets
left = lca(node.left, p, q)
right = lca(node.right, p, q)
if left is not null and right is not null:
return node // p and q are in different subtreesreturn left if left is not null else right
// O(n)
LCA: if both subtrees return non-null, current node is the answer:
The insight is that if p and q are in different subtrees, the current node is their LCA โ both recursive calls return non-null simultaneously only when they are on opposite sides.
If both are in the same subtree, only one call returns non-null and that result propagates upward.
Common Mistake: Forgetting the null base case at the top of every recursive tree function. Without if (node == null) return ..., the function throws NullPointerException when it reaches a leaf and tries to access node.left or node.right. Every recursive tree function must handle the null case first โ it is not optional.
07
Section Seven ยท Recursion
Recursion on Trees โ The Universal Pattern
Every binary tree algorithm โ traversal, search, height, LCA, path sum, serialisation โ is a variation of the same three-line recursive skeleton: handle the null case, process or recurse into the left subtree, process or recurse into the right subtree. The only decisions are: what order to do these three things (pre, in, or post), what information to pass down (top-down), and what information to return up (bottom-up). Internalising this template means that when you see a new tree problem, you are not inventing an algorithm โ you are choosing values for three variables in a template you already know.
The Universal Tree Recursion Template
Java โ The Template
// THE TEMPLATE โ every binary tree problem fits this skeletonReturnTypesolve(TreeNode node /* optional state passed down */) {
// โ BASE CASE โ always check null firstif (node == null) return BASE_VALUE;
// โก PREORDER WORK โ do something BEFORE recursing// Use when: parent info needed by children// Example: path problems, serialisation, accumulating prefixdoSomethingBefore(node);
// โข RECURSE INTO CHILDRENReturnType leftResult = solve(node.left /* updated state */);
ReturnType rightResult = solve(node.right /* updated state */);
// โฃ POSTORDER WORK โ do something AFTER recursing// Use when: children results needed to compute current// Example: height, size, LCA, balance checkreturncombine(node, leftResult, rightResult);
}
// โโโ CONCRETE EXAMPLES filling the template โโโโโโโโโโโโโโโโโโโโโ// Height: BASE=-1, combine = 1 + max(L, R)// Count: BASE=0, combine = 1 + L + R// Sum: BASE=0, combine = node.val + L + R// Max path: BASE=0, global max updated in combine// Balanced: BASE=0, combine = |L-R|>1 ? -1 : 1+max(L,R)// Inorder: no return, visit between left and right recurse
Top-Down (Preorder)
Pass information DOWN
The parent computes something and passes it to the children as a parameter. Children use the passed value, not their own subtree results, to make decisions.
Path sum check (pass remaining target down)
Max depth from root (pass current depth down)
Path encoding (pass current path string down)
BST validation (pass min/max bounds down)
Bottom-Up (Postorder)
Return information UP
Children compute something and return it to the parent. The parent combines the children's results to compute its own result. Information flows from leaves to root.
Tree height (children return heights)
Tree size (children return counts)
LCA (children return found nodes)
Balance check (children return heights or -1)
Recursion Strategy Decision
Every tree problem in an interview maps to this template:
Before writing any code for a tree problem, identify: (1) what the function returns โ single value, list, boolean, or void with side effects; (2) what the base case is for null; (3) whether you need top-down or bottom-up.
Once these three are decided, the code is structural.
08
Section Eight ยท Implementation
Build It Once
Build this from scratch once โ it makes the mechanics concrete.
In any real project or interview, the standard LeetCode TreeNode is already provided.
Java โ Binary Tree core mechanics
// The LeetCode TreeNode is the standard interview definition.// Build tree operations using recursion โ not iteration โ first.public classTreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) { this.val = val; }
}
// Height โ postorder bottom-up: O(n)intheight(TreeNode node) {
if (node == null) return -1;
return 1 + Math.max(height(node.left), height(node.right));
}
// Inorder โ recursive, appends to result list: O(n)voidinorder(TreeNode node, List<Integer> result) {
if (node == null) return;
inorder(node.left, result);
result.add(node.val); // visit between childreninorder(node.right, result);
}
// isBalanced โ O(n) single pass with -1 sentinelbooleanisBalanced(TreeNode root) {
returncheckHeight(root) != -1;
}
intcheckHeight(TreeNode node) {
if (node == null) return 0;
int l = checkHeight(node.left);
if (l == -1) return -1; // short-circuitint r = checkHeight(node.right);
if (r == -1 || Math.abs(l - r) > 1) return -1;
return 1 + Math.max(l, r);
}
Binary Tree โ Full Implementation
Java โ Complete Binary Tree implementation
import java.util.*;
public class BinaryTree {
static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val; this.left = left; this.right = right;
}
}
// โโโ INSERT (level-order โ maintains complete tree) โโโ O(n)
static TreeNode insert(TreeNode root, int val) {
TreeNode newNode = new TreeNode(val);
if (root == null) return newNode;
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
TreeNode curr = q.poll();
if (curr.left == null) { curr.left = newNode; return root; }
else q.add(curr.left);
if (curr.right == null) { curr.right = newNode; return root; }
else q.add(curr.right);
}
return root;
}
// โโโ INORDER (Left โ Root โ Right) โโโ O(n)
static void inorder(TreeNode node, List<Integer> result) {
if (node == null) return;
inorder(node.left, result);
result.add(node.val);
inorder(node.right, result);
}
// โโโ PREORDER (Root โ Left โ Right) โโโ O(n)
static void preorder(TreeNode node, List<Integer> result) {
if (node == null) return;
result.add(node.val);
preorder(node.left, result);
preorder(node.right, result);
}
// โโโ POSTORDER (Left โ Right โ Root) โโโ O(n)
static void postorder(TreeNode node, List<Integer> result) {
if (node == null) return;
postorder(node.left, result);
postorder(node.right, result);
result.add(node.val);
}
// โโโ LEVEL ORDER โโโ O(n)
static List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
int size = q.size();
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
level.add(node.val);
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
result.add(level);
}
return result;
}
// โโโ HEIGHT โโโ O(n)
static int height(TreeNode node) {
if (node == null) return -1;
return 1 + Math.max(height(node.left), height(node.right));
}
// โโโ COUNT NODES โโโ O(n)
static int countNodes(TreeNode node) {
if (node == null) return 0;
return 1 + countNodes(node.left) + countNodes(node.right);
}
// โโโ IS BALANCED โโโ O(n)
static boolean isBalanced(TreeNode root) {
return checkHeight(root) != -1;
}
static int checkHeight(TreeNode node) {
if (node == null) return 0;
int l = checkHeight(node.left);
if (l == -1) return -1;
int r = checkHeight(node.right);
if (r == -1 || Math.abs(l - r) > 1) return -1;
return 1 + Math.max(l, r);
}
// โโโ IS SYMMETRIC โโโ O(n)
static boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return isMirror(root.left, root.right);
}
static boolean isMirror(TreeNode a, TreeNode b) {
if (a == null && b == null) return true;
if (a == null || b == null) return false;
return a.val == b.val
&& isMirror(a.left, b.right)
&& isMirror(a.right, b.left);
}
// โโโ LOWEST COMMON ANCESTOR โโโ O(n)
static TreeNode lca(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lca(root.left, p, q);
TreeNode right = lca(root.right, p, q);
if (left != null && right != null) return root;
return left != null ? left : right;
}
// โโโ MAX PATH SUM โโโ O(n)
static int maxSum;
static int maxPathSum(TreeNode root) {
maxSum = Integer.MIN_VALUE;
maxGain(root);
return maxSum;
}
static int maxGain(TreeNode node) {
if (node == null) return 0;
int leftGain = Math.max(0, maxGain(node.left));
int rightGain = Math.max(0, maxGain(node.right));
maxSum = Math.max(maxSum, node.val + leftGain + rightGain);
return node.val + Math.max(leftGain, rightGain);
}
// โโโ SERIALIZE (preorder with "null" markers) โโโ O(n)
static String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serHelper(root, sb);
return sb.toString();
}
static void serHelper(TreeNode node, StringBuilder sb) {
if (node == null) { sb.append("null,"); return; }
sb.append(node.val).append(",");
serHelper(node.left, sb);
serHelper(node.right, sb);
}
// โโโ DESERIALIZE โโโ O(n)
static TreeNode deserialize(String data) {
Queue<String> tokens = new LinkedList<>(Arrays.asList(data.split(",")));
return desHelper(tokens);
}
static TreeNode desHelper(Queue<String> tokens) {
String val = tokens.poll();
if (val.equals("null")) return null;
TreeNode node = new TreeNode(Integer.parseInt(val));
node.left = desHelper(tokens);
node.right = desHelper(tokens);
return node;
}
// โโโ MAIN โ 5 examples โโโ
public static void main(String[] args) {
// Build tree: [8, 3, 10, 1, 6, 9, 14]
TreeNode root = new TreeNode(8);
root.left = new TreeNode(3, new TreeNode(1), new TreeNode(6));
root.right = new TreeNode(10, new TreeNode(9), new TreeNode(14));
// 1. All 4 traversals
List<Integer> in = new ArrayList<>(); inorder(root, in);
List<Integer> pre = new ArrayList<>(); preorder(root, pre);
List<Integer> post = new ArrayList<>(); postorder(root, post);
System.out.println("Inorder: " + in); // [1,3,6,8,9,10,14]
System.out.println("Preorder: " + pre); // [8,3,1,6,10,9,14]
System.out.println("Postorder: " + post); // [1,6,3,9,14,10,8]
System.out.println("Level-order:" + levelOrder(root));
// 2. Height and count
System.out.println("Height: " + height(root)); // 2
System.out.println("Nodes: " + countNodes(root)); // 7
// 3. Balance check
System.out.println("Balanced: " + isBalanced(root)); // true
// 4. LCA of nodes 1 and 6
TreeNode ancestor = lca(root, root.left.left, root.left.right);
System.out.println("LCA(1,6): " + ancestor.val); // 3
// 5. Max path sum
System.out.println("Max path sum: " + maxPathSum(root)); // 8+10+14=32
}
}
Python โ Binary Tree implementation
from collections import deque
from typing import Optional, List
# Standard LeetCode TreeNode (same as Java version)
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# โโโ HEIGHT โโโ O(n)
def height(node: Optional[TreeNode]) -> int:
if not node:
return -1
return 1 + max(height(node.left), height(node.right))
# โโโ COUNT NODES โโโ O(n)
def count_nodes(node: Optional[TreeNode]) -> int:
if not node:
return 0
return 1 + count_nodes(node.left) + count_nodes(node.right)
# โโโ INORDER โโโ O(n)
def inorder(node: Optional[TreeNode], result: List[int]) -> None:
if not node:
return
inorder(node.left, result)
result.append(node.val)
inorder(node.right, result)
# โโโ PREORDER โโโ O(n)
def preorder(node: Optional[TreeNode], result: List[int]) -> None:
if not node:
return
result.append(node.val)
preorder(node.left, result)
preorder(node.right, result)
# โโโ POSTORDER โโโ O(n)
def postorder(node: Optional[TreeNode], result: List[int]) -> None:
if not node:
return
postorder(node.left, result)
postorder(node.right, result)
result.append(node.val)
# โโโ LEVEL ORDER โโโ O(n)
def level_order(root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
result, queue = [], deque([root])
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
result.append(level)
return result
# โโโ IS BALANCED โโโ O(n)
def is_balanced(root: Optional[TreeNode]) -> bool:
def check(node):
if not node:
return 0
l = check(node.left)
if l == -1: return -1
r = check(node.right)
if r == -1 or abs(l - r) > 1: return -1
return 1 + max(l, r)
return check(root) != -1
# โโโ LOWEST COMMON ANCESTOR โโโ O(n)
def lca(root, p, q):
if not root or root == p or root == q:
return root
left = lca(root.left, p, q)
right = lca(root.right, p, q)
if left and right:
return root
return left if left else right
# โโโ MAX PATH SUM โโโ O(n)
def max_path_sum(root: Optional[TreeNode]) -> int:
max_sum = float('-inf')
def gain(node):
nonlocal max_sum
if not node:
return 0
l = max(0, gain(node.left))
r = max(0, gain(node.right))
max_sum = max(max_sum, node.val + l + r)
return node.val + max(l, r)
gain(root)
return max_sum
09
Section Nine ยท Java Reference
Use It In Java
IN JAVA โ No Built-in Binary Tree
UseNo built-in BinaryTree โ build TreeNode from scratch. For sorted-tree operations: TreeMap<K,V> or TreeSet<E>
โ Gotcha: Java's TreeMap and TreeSet are balanced BSTs (red-black trees) but expose no node-level API โ you cannot traverse the tree structure, access parent pointers, or perform tree rotations. For any problem requiring node-level tree manipulation (LCA, path sum, serialisation), build TreeNode from scratch.
ChooseUse TreeMap/TreeSet when you need sorted collections with O(log n) operations. Use a custom TreeNode for any tree structure problem, algorithm implementation, or interview question involving tree manipulation directly.
10
Section Ten ยท Practice
Problems To Solve
Binary tree interview problems almost always require one of two approaches: DFS recursion using the universal postorder/preorder template (height, balance, LCA, path sum, serialisation), or BFS level-order traversal with a queue (minimum depth, level-by-level processing, connect next pointers). When you read a tree problem, ask two questions first: does the answer depend on processing children before the parent (postorder DFS), or does it require processing nodes level by level (BFS)? The answer determines your entire algorithm structure.
At each node, the best path through it is node.val + max(0, left gain) + max(0, right gain). Track global max while returning only the best single-branch gain upward.
Interview Pattern:
For 90% of binary tree problems, write the recursive function signature first, then the null base case, then decide: does the current node need information passed down from the parent (top-down), or does it need results returned from children (bottom-up)?