Comprehensive theory, algorithmic patterns, templates, and problem catalog for Binary Trees, Binary Search Trees (BST), and Tree Traversals.
A Tree is a hierarchical non-linear data structure consisting of nodes connected by edges, with no cycles.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};- Pre-order (Root -> Left -> Right): Used for serialization/cloning.
- In-order (Left -> Root -> Right): Yields sorted order in a Binary Search Tree (BST).
- Post-order (Left -> Right -> Root): Bottom-up evaluation (e.g., maximum depth, diameter, deleting a tree).
- Level-order (Breadth-First Search): Uses a
std::queueto traverse layer by layer.
#include <queue>
vector<vector<int>> levelOrder(TreeNode* root) {
if (!root) return {};
vector<vector<int>> result;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size();
vector<int> currentLevel;
for (int i = 0; i < levelSize; ++i) {
TreeNode* node = q.front();
q.pop();
currentLevel.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
result.push_back(currentLevel);
}
return result;
}// Maximum Depth of Binary Tree
int maxDepth(TreeNode* root) {
if (!root) return 0;
return 1 + max(maxDepth(root->left), maxDepth(root->right));
}
// Diameter of Binary Tree
int calculateDiameter(TreeNode* root, int& maxDiameter) {
if (!root) return 0;
int leftHeight = calculateDiameter(root->left, maxDiameter);
int rightHeight = calculateDiameter(root->right, maxDiameter);
maxDiameter = max(maxDiameter, leftHeight + rightHeight);
return 1 + max(leftHeight, rightHeight);
}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root; // p and q are on separate subtrees
return left ? left : right;
}When answering many path queries on a general tree of size
- Precompute depths and
$2^k$ -th ancestors with BFS/DFS:$\text{up}[k][u] = \text{up}[k - 1][\text{up}[k - 1][u]]$ . - For query
$(u, v)$ , equalize depths, lift simultaneously to find$\text{LCA}(u, v)$ in$\mathcal{O}(\log N)$ . - Path length
$d = \text{depth}[u] + \text{depth}[v] - 2 \cdot \text{depth}[\text{LCA}(u, v)]$ .
When matching longest common suffixes/prefixes with multi-criterion tiebreaking:
- Reverse strings to transform suffix matching into prefix matching on a Trie.
- Augment each
TrieNodewith optimal subtree properties (e.g.minLen,bestIdx). - During insertion in natural index order, update node metadata with strict comparison (
len < node->minLen) to automatically break ties toward earlier indices.
When finding the maximum path sum across any simple path in a binary tree:
-
Branch Gain ($\text{gain}(u)$): Maximum gain from
$u$ extending downward:$u.\text{val} + \max(0, \max(\text{leftGain}, \text{rightGain}))$ . -
Apex Path Sum: Path turning at
$u$ :$u.\text{val} + \max(0, \text{leftGain}) + \max(0, \text{rightGain})$ . - Update global maximum across all nodes in
$\mathcal{O}(N)$ post-order traversal.
When finding the
-
Denary Tree Abstraction: Numbers
$1 \dots n$ form a 10-ary tree where node$x$ has children$[10x, 10x+9]$ . Pre-order traversal corresponds to lexicographical order. -
Subtree Size Counting: Count numbers sharing prefix
currby expanding intervals$[first, last)$ with$\times 10$ , summing$\min(n + 1, last) - first$ . -
Branching:
- If
$\text{steps} \le k$ : target lies outside subtree$\to$ skip subtree ($k \gets k - \text{steps}, curr \gets curr + 1$ ). - If
$\text{steps} > k$ : target lies inside subtree$\to$ descend down ($k \gets k - 1, curr \gets curr \times 10$ ).
- If
-
Complexity:
$\mathcal{O}((\log_{10} n)^2)$ time and$\mathcal{O}(1)$ space.
When serializing and reconstructing arbitrary binary tree structures:
-
Preorder DFS Encoding: Traverse
Root -> Left -> Right. Append"# "for null pointers andto_string(val) + " "for real nodes. -
Deterministic Deserialization: Wrap serialized tokens in an
istringstream. Each recursive call extracts a token: if"#"returnnullptr, else constructTreeNode(val), assignleft = deserializeHelper(in),right = deserializeHelper(in), and return the root. -
Complexity:
$\mathcal{O}(N)$ time and$\mathcal{O}(N)$ space.
-
Skewed Trees & Recursion Depth: For degenerate linked-list trees, recursive DFS uses
$\mathcal{O}(N)$ stack space and can cause stack overflow. -
BST Validation: Validating BST cannot just check if
root->left->val < root->val. The left subtree must be strictly less thanroot->valfor ALL nodes. Always pass valid interval ranges(low, high)usinglong long. -
Empty Trees: Always handle
root == nullptras the first base case. -
Log Table Size: For
$N \le 10^5$ ,$\lceil \log_2(10^5) \rceil = 17$ , use table size$18$ . -
All-Negative Node Trees: Initialize global maxima to
INT_MINso single least-negative node values are picked correctly.
| # | Title | Difficulty | Time | Space | Solution Link |
|---|---|---|---|---|---|
| 124 | Binary Tree Maximum Path Sum | Hard |
C++ | ||
| 297 | Serialize and Deserialize Binary Tree | Hard |
C++ | ||
| 440 | K-th Smallest in Lexicographical Order | Hard |
C++ | ||
| 745 | Prefix and Suffix Search | Hard |
|
C++ | |
| 834 | Sum of Distances in Tree | Hard |
C++ | ||
| 968 | Binary Tree Cameras | Hard |
C++ | ||
| 987 | Vertical Order Traversal of a Binary Tree | Hard |
C++ | ||
| 3093 | Longest Common Suffix Queries | Hard |
$\mathcal{O}(\sum | W_c | + \sum |
| 3559 | Number of Ways to Assign Edge Weights II | Hard |
C++ |