One place to revise everything about Trees before your upcoming interview.


1️⃣ What is a Tree?

A Tree is a non-linear, hierarchical data structure where a single node (the root) connects to zero or more child nodes, forming a recursive structure.

graph TD
    1((1)) --> 2((2))
    1 --> 3((3))
    2 --> 4((4))
    2 --> 5((5))
    3 --> 6((6))
    3 --> 7((7))

    classDef default fill:#1e1e2e,stroke:#cba6f7,stroke-width:2px,color:#cdd6f4;
    classDef root fill:#f38ba8,stroke:#e64553,stroke-width:2px,color:#11111b;
    class 1 root;

Key Terminology

TermDefinition
NodeAn element that holds data
RootThe topmost node (no parent)
ParentA node directly above another
ChildA node directly below another
SiblingsNodes sharing the same parent
LeafA node with no children
DepthNumber of edges from root to the node
HeightNumber of edges from the node to the deepest leaf
AncestorAny node on the path from a node up to the root
DescendantAny node reachable going down from a node

Types of Binary Trees

TypeProperty
Full (Strict)Every node has exactly 0 or 2 children
CompleteAll levels completely filled except last, which is filled left-to-right
PerfectAll internal nodes have 2 children AND all leaves are at the same level
BalancedHeight of left and right subtrees differ by at most 1
Skewed (Degenerate)Every node has at most 1 child β€” essentially a linked list
BST (Binary Search Tree)Left subtree values < root < right subtree values

Node Structure

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

2️⃣ The Two Traversal Paradigms

Every tree problem boils down to how you traverse the tree:

ParadigmData StructureWhen to Use
DFSStack / RecursionPath problems, tree properties, subtree computation
BFSQueueLevel-by-level problems, shortest path in tree, side views

The Golden Rule

  • DFS = go deep first (recursive or with explicit stack).
  • BFS = go wide first (always with a queue, level by level).
  • If the problem says β€œlevel” or β€œlayer” β†’ think BFS.
  • If the problem says β€œpath” or β€œsubtree” β†’ think DFS.

3️⃣ DFS Traversals β€” Recursive

NOTE

  • All recursive traversals: Time β€” visit each node once.
  • Space β€” recursion stack, where = height (worst case for skewed tree).

Traversal Order Reference

          1
        /   \
       2     3
      / \
     4   5

Inorder   (L β†’ Root β†’ R):  4 2 5 1 3
Preorder  (Root β†’ L β†’ R):  1 2 4 5 3
Postorder (L β†’ R β†’ Root):  4 5 2 3 1

Memory Trick

  • Inorder β†’ root in between left & right
  • Preorder β†’ root comes before (pre) everything
  • Postorder β†’ root comes after (post) everything

Inorder (Left β†’ Root β†’ Right) β€” LC 94

void inorder(TreeNode* root, vector<int>& res) {
    if (!root) return;
    inorder(root->left, res);
    res.push_back(root->val);   // Process at root
    inorder(root->right, res);
}

Why Inorder Matters for BST

  • Inorder traversal of a BST gives elements in sorted ascending order.
  • This is the basis for BST validation, kth smallest, predecessor/successor etc.

Preorder (Root β†’ Left β†’ Right) β€” LC 144

void preorder(TreeNode* root, vector<int>& res) {
    if (!root) return;
    res.push_back(root->val);   // Process at root
    preorder(root->left, res);
    preorder(root->right, res);
}

Postorder (Left β†’ Right β†’ Root) β€” LC 145

void postorder(TreeNode* root, vector<int>& res) {
    if (!root) return;
    postorder(root->left, res);
    postorder(root->right, res);
    res.push_back(root->val);   // Process at root
}

When to Use Which DFS Order

  • Preorder β€” when you need to process root before children (e.g., cloning a tree, serialization)
  • Inorder β€” when you need sorted order in BST
  • Postorder β€” when you need to process children before root (e.g., computing height, deleting nodes, freeing memory)

4️⃣ DFS Traversals β€” Iterative

Unified Pattern

Both iterative inorder and preorder follow the same skeleton β€” the only difference is where you record root->val. This makes them easy to memorize together.

Iterative Inorder β€” LC 94

Go left as far as possible, push along the way. When you can’t go left, pop and go right.

vector<int> inorderTraversal(TreeNode* root) {
    stack<TreeNode*> st;
    vector<int> ans;
    while (true) {
        if (root) {
            st.push(root);
            root = root->left;
        } else {
            if (st.empty()) break;
            root = st.top(); st.pop();
            ans.push_back(root->val);    // Process AFTER popping (in-between)
            root = root->right;
        }
    }
    return ans;
}

Iterative Preorder β€” LC 144

Same skeleton β€” just record the value before pushing left (process root first).

vector<int> preorderTraversal(TreeNode* root) {
    stack<TreeNode*> st;
    vector<int> ans;
    while (true) {
        if (root) {
            ans.push_back(root->val);    // Process BEFORE going left (pre = root first)
            st.push(root);
            root = root->left;
        } else {
            if (st.empty()) break;
            root = st.top(); st.pop();
            root = root->right;
        }
    }
    return ans;
}

Inorder vs Preorder β€” Spot the Difference

INORDER:   push left β†’ pop β†’ RECORD β†’ go right
PREORDER:  RECORD β†’ push left β†’ pop β†’ go right

That’s it. One line moves. Everything else is identical.

Iterative Postorder (2-Stack β€” Easy Way) β€” LC 145

Trick: Modified preorder (Root β†’ Right β†’ Left) then reverse gives (Left β†’ Right β†’ Root).

vector<int> postorderTraversal(TreeNode* root) {
    vector<int> res;
    if (!root) return res;
    stack<TreeNode*> st;
    st.push(root);
 
    while (!st.empty()) {
        TreeNode* curr = st.top(); st.pop();
        res.push_back(curr->val);
 
        if (curr->left)  st.push(curr->left);   // Left first (opposite of preorder)
        if (curr->right) st.push(curr->right);   // Right second
    }
    reverse(res.begin(), res.end());             // Reverse entire result
    return res;
}

Iterative Postorder (1-Stack β€” Optimal) β€” LC 145

All-in-One Traversal (Pre + In + Post in Single Pass)


5️⃣ Morris Traversal β€” O(1) Space, No Stack, No Recursion

Why Morris?

  • Both recursive and iterative DFS use space (call stack or explicit stack).
  • Morris Traversal achieves extra space by temporarily modifying the tree β€” creating threaded links from the rightmost node of the left subtree back to the current node, then restoring the tree afterward.

Morris Inorder Traversal β€” LC 94

Idea: For each node, find the inorder predecessor (rightmost node in left subtree). Thread its right pointer back to the current node. This lets us return to the parent without a stack.

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> ans;
    while (root != NULL) {
        if (root->left == NULL) {
            ans.push_back(root->val);     // No left subtree β†’ process & go right
            root = root->right;
        } else {
            auto leftNode = root->left;
            while (leftNode->right) leftNode = leftNode->right;   // Find predecessor
            leftNode->right = root;       // Create thread back to root
 
            auto tem = root;
            root = root->left;            // Move to left subtree
            tem->left = NULL;             // Break original left link (avoid infinite loop)
        }
    }
    return ans;
}
// TC: O(N) β€” each edge traversed at most 2 times | SC: O(1)

How Morris Works β€” Step by Step

Original:       1
              /   \
             2     3
            / \
           4   5

Step 1: root=1, left exists β†’ find predecessor of 1 in left subtree
        Predecessor = 5 (rightmost in left subtree of 1)
        Thread: 5->right = 1, move to root->left (root=2), break 1->left

Step 2: root=2, left exists β†’ predecessor = 4
        Thread: 4->right = 2, move to root->left (root=4), break 2->left

Step 3: root=4, no left β†’ PRINT 4, go right (thread takes us to 2)

Step 4: root=2, no left β†’ PRINT 2, go right (thread takes us to 5)

Step 5: root=5, no left β†’ PRINT 5, go right (thread takes us to 1)

Step 6: root=1, no left β†’ PRINT 1, go right (root=3)

Step 7: root=3, no left β†’ PRINT 3, go right (NULL) β†’ done

Output: 4 2 5 1 3 βœ“ (correct inorder)

5️⃣ BFS / Level Order Traversal

Basic Level Order β€” LC 102

vector<int> levelOrder(TreeNode* root) {
    if(!root) return {};
    queue<TreeNode*> q;
    q.push(root);
    vector<int> ans;
 
    while (!q.empty()) {
        auto curr = q.front(); q.pop();
        ans.push_back(curr->val);
        if (curr->left)  q.push(curr->left);
        if (curr->right) q.push(curr->right);
    }
    return ans;
}

Level-by-Level (The Template You’ll Use 90% of the Time) β€” LC 102 πŸ”₯

vector<vector<int>> levelOrder(TreeNode* root) {
    if(!root) return {};
    queue<TreeNode*> q;
    q.push(root);
    vector<vector<int>> ans;
    while(!q.empty())
    {
        vector<int> level;
        int sz = q.size();
        while(sz--) {
            auto curr = q.front(); q.pop();
            level.push_back(curr->val);
            if(curr->left) q.push(curr->left);
            if(curr->right) q.push(curr->right);
        } 
        ans.push_back(level);
    }
    return ans;
}
// TC: O(N) | SC: O(N) β€” queue holds at most one level width

The Level-by-Level BFS Template

  • If you want to process nodes level by level
  • This single template solves: zigzag, right side view, left side view, max width, average per level, etc.

Zigzag Level Order β€” LC 103

Same template β€” just reverse alternate levels:

vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
    if(!root) return {};
    queue<TreeNode*> q;
    q.push(root);
    vector<vector<int>> ans;
    bool leftToRight = true;
 
    while (!q.empty()) {
        vector<int> level;
        int sz = q.size();
 
        while (sz--) {
            auto curr = q.front(); q.pop();
            level.push_back(curr->val);
            if (curr->left)  q.push(curr->left);
            if (curr->right) q.push(curr->right);
        }
        if (!leftToRight) reverse(level.begin(), level.end());
        ans.push_back(level);
        leftToRight = !leftToRight;
    }
    return ans;
}

6️⃣ Tree Properties β€” The Foundation Problems

Maximum Depth β€” LC 104

int maxDepth(TreeNode* root) {
    if (!root) return 0;
    return 1 + max(maxDepth(root->left), maxDepth(root->right));
}

Minimum Depth β€” LC 111

Gotcha

  • If one child is null, you must go to the other child, not return 0.
  • A node with only a right child is NOT a leaf β€” the minimum depth goes through the right subtree.
int minDepth(TreeNode* root) {
    if (!root) return 0;
    if (!root->left)  return 1 + minDepth(root->right);
    if (!root->right) return 1 + minDepth(root->left);
    return 1 + min(minDepth(root->left), minDepth(root->right));
}

Diameter of Binary Tree β€” LC 543

TheΒ diameterΒ of a binary tree is theΒ lengthΒ of the longest path between any two nodes in a tree. This path may or may not pass through theΒ root. The diameter passes through a node = leftHeight + rightHeight. Track global max.

int diameterOfBinaryTree(TreeNode* root) {
    int diameter = 0;
    height(root, diameter);
    return diameter;
}
 
int height(TreeNode* root, int& diameter) {
    if (!root) return 0;
    int lh = height(root->left, diameter);
    int rh = height(root->right, diameter);
    diameter = max(diameter, lh + rh);    // Update diameter at this node
    return 1 + max(lh, rh);              // Return height
}

Balanced Binary Tree β€” LC 110 O(N)

Use βˆ’1 as sentinel for unbalanced. Avoid recomputing heights.

int height(TreeNode* root) {
    if(!root) return 0;
    int lh = height(root->left);
    int rh = height(root->right);
    if(lh == -1 or rh == -1) return -1;
    if(abs(lh-rh) > 1) return -1;
    return 1 + max(lh, rh); 
}
 
bool isBalanced(TreeNode* root) {
    return height(root) == -1 ? false : true;
}

Same Tree β€” LC 100

bool isSameTree(TreeNode* p, TreeNode* q) {
    if (!p && !q) return true;
    if ((!p and q) or (!q and p)) return false;
    return p->val == q->val
        && isSameTree(p->left, q->left)
        && isSameTree(p->right, q->right);
}

Symmetric Tree β€” LC 101

Mirror comparison β€” compare left.left ↔ right.right and left.right ↔ right.left:

bool isSymmetric(TreeNode* root) {
    if (!root) return true;
    return isMirror(root->left, root->right);
}
 
bool isMirror(TreeNode* p, TreeNode* q) {
    if (!p && !q) return true;
    if ((!p and q) or (!q and p)) return false;
    return p->val == q->val
        && isMirror(p->left, q->right)
        && isMirror(p->right, q->left);
}

Invert Binary Tree β€” LC 226

TreeNode* invertTree(TreeNode* root) {
    if (!root) return nullptr;
    swap(root->left, root->right);
    invertTree(root->left);
    invertTree(root->right);
    return root;
}

7️⃣ Side Views of a Binary Tree

Right Side View β€” LC 199

BFS approach β€” last node of each level:

vector<int> rightSideView(TreeNode* root) {
    if(!root) return {};
    queue<TreeNode*> q;
    q.push(root);
    vector<int> ans;
 
    while (!q.empty()) {
        int sz = q.size();
        while (sz--) {
            auto curr = q.front(); q.pop();
            if (sz == 0) ans.push_back(curr->val);  // Last of level
            if (curr->left)  q.push(curr->left);
            if (curr->right) q.push(curr->right);
        }
    }
    return ans;
}

DFS approach β€” visit right child first, add first node at each depth:

vector<int> rightSideView(TreeNode* root) {
    vector<int> res;
    dfs(root, res, 0);
    return res;
}
 
void dfs(TreeNode* root, vector<int>& res, int level) {
    if (!root) return;
    if (res.size() == level)          // First node at this depth
        res.push_back(root->val);
    dfs(root->right, res, level + 1); // Right first for right view
    dfs(root->left, res, level + 1);
}

Left Side View β€” LC 513

Same DFS pattern β€” just visit left child first:

void dfs(TreeNode* root, vector<int>& res, int level) {
    if (!root) return;
    if (res.size() == level)
        res.push_back(root->val);
    dfs(root->left, res, level + 1);  // Left first for left view
    dfs(root->right, res, level + 1);
}

Top View & Bottom View

Use Horizontal Distance (HD): root = 0, left child = HDβˆ’1, right child = HD+1.

Top View β€” BFS, keep first node at each HD:

vector<int> topView(TreeNode* root) {
    if (!root) return {};
    map<int, int> hdMap;                         // HD β†’ node value (ordered map)
    queue<pair<TreeNode*, int>> q;               // node, hd
    q.push({root, 0});
 
    while (!q.empty()) {
        auto [node, hd] = q.front(); q.pop();
        if (!hdMap.count(hd))        // First node at this HD wins
            hdMap[hd] = node->val;
        if (node->left)  q.push({node->left, hd - 1});
        if (node->right) q.push({node->right, hd + 1});
    }
 
    vector<int> res;
    for (auto& [hd, val] : hdMap) res.push_back(val);
    return res;
}

Bottom View β€” same BFS, but overwrite at each HD (last node wins):

// Only difference from Top View: remove the if-check, always overwrite
hdMap[hd] = node->val;  // Every node overwrites β†’ last (deepest) wins

Top View vs Bottom View

  • Top View β†’ if (!map.count(hd)) β€” first node at each HD (BFS ensures shallowest first)
  • Bottom View β†’ always overwrite β€” last node at each HD (BFS ensures deepest last)
  • Both use map<int, int> (ordered by HD) to get left-to-right order.

Boundary Traversal (Anti-clockwise) β€” LC 545

Three parts: Left boundary (top-down, exclude leaf) β†’ All leaves (left-to-right) β†’ Right boundary (bottom-up, exclude leaf).

bool isLeaf(TreeNode* root) {
    return !root->left && !root->right;
}
 
void leftBoundary(TreeNode* root, vector<int>& ans) {
    root = root->left;
    while (root) {
        if (!isLeaf(root))
            ans.push_back(root->val);
        if (root->left)
            root = root->left;
        else
            root = root->right;
    }
}
 
void leaves(TreeNode* root, vector<int>& ans) {
    if (!root) return;
    if (isLeaf(root)) ans.push_back(root->val);
    leaves(root->left, ans);
    leaves(root->right, ans);
}
 
void rightBoundary(TreeNode* root, vector<int>& ans) {
    root = root->right;
    vector<int> r;
    while (root) {
        if (!isLeaf(root))
            r.push_back(root->val);
        if (root->right)
            root = root->right;
        else
            root = root->left;
    }
    reverse(r.begin(), r.end());
    for (auto &i : r) ans.push_back(i);
}
 
vector<int> boundaryTraversal(TreeNode* root) {
    if (!root) return {};
    if (isLeaf(root)) return {root->val};
 
    vector<int> ans;
    ans.push_back(root->val);
    leftBoundary(root, ans);
    leaves(root, ans);
    rightBoundary(root, ans);
    return ans;
}

8️⃣ Path Problems

Path Sum (Root to Leaf) β€” LC 112

bool hasPathSum(TreeNode* root, int target) {
	if (!root) return false;
	t -= root->val;
	if (!root->left && !root->right && t == 0) return true; // Leaf check
 
	return hasPathSum(root->left, t) || hasPathSum(root->right, t);
}

Path Sum III β€” LC 437 πŸ”₯

int pathSum(TreeNode* root, int targetSum) {
    unordered_map<long long, int> prefixMap;
    prefixMap[0] = 1;  // Empty prefix
    return dfs(root, 0, targetSum, prefixMap);
}
 
int dfs(TreeNode* root, long long currSum, int target,
        unordered_map<long long, int>& prefixMap) {
    if (!root) return 0;
 
    currSum += root->val;
    int count = prefixMap[currSum - target];   // Paths ending here
 
    prefixMap[currSum]++;                       // Add current prefix
    count += dfs(root->left, currSum, target, prefixMap);
    count += dfs(root->right, currSum, target, prefixMap);
    prefixMap[currSum]--;                       // Backtrack
 
    return count;
}
// TC: O(N) | SC: O(N)

Path Sum III Pattern

  • This is the prefix sum + hashmap technique adapted for trees.
  • currSum - target existing in the prefix map means there’s a sub-path summing to target.
  • Backtrack the prefix map when returning from a subtree β€” crucial for correctness.

Binary Tree Maximum Path Sum β€” LC 124 πŸ”₯

Any node to any node (not necessarily root-to-leaf). Classic interview problem.

int maxPathSum(TreeNode* root) {
    int maxSum = INT_MIN;
    maxGain(root, maxSum);
    return maxSum;
}
 
int maxGain(TreeNode* root, int& maxSum) {
    if (!root) return 0;
    int left  = max(0, maxGain(root->left, maxSum));   // Ignore negative paths
    int right = max(0, maxGain(root->right, maxSum));
 
    maxSum = max(maxSum, left + root->val + right);    // Path through this node
    return root->val + max(left, right);               // Return best single branch
}

Key Insight

  • At each node, the path through it = leftGain + val + rightGain (this updates global max).
  • But we can only return one branch upward (left OR right, not both) β€” a path can’t fork.

9️⃣ Lowest Common Ancestor (LCA)

LCA of Binary Tree β€” LC 236 πŸ”₯

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 in different subtrees
    return left ? left : right;       // Both in same subtree
}
// TC: O(N) | SC: O(H)

LCA Intuition

  • If p is found in the left subtree and q in the right subtree β†’ current root is the LCA.
  • If both found on one side β†’ the LCA is deeper on that side (returned by recursive call).

LCA of BST β€” LC 235 O(H)

Use BST property to avoid searching unnecessary subtrees:

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (p->val < root->val && q->val < root->val)
        return lowestCommonAncestor(root->left, p, q);
    if (p->val > root->val && q->val > root->val)
        return lowestCommonAncestor(root->right, p, q);
    return root;  // Split point β€” this is the LCA
}

πŸ”Ÿ BST-Specific Patterns

Validate BST β€” LC 98

Pass (min, max) range down. Every node must be within its valid range.

bool isValidBST(TreeNode* root) {
    return validate(root, LONG_MIN, LONG_MAX);
}
 
bool validate(TreeNode* root, long minVal, long maxVal) {
    if (!root) return true;
    if (root->val <= minVal || root->val >= maxVal) return false;
    return validate(root->left, minVal, root->val)
        && validate(root->right, root->val, maxVal);
}

Kth Smallest Element in BST β€” LC 230

Inorder traversal gives sorted order. Just count to k.

int kthSmallest(TreeNode* root, int k) {
    stack<TreeNode*> st;
    while (root || !st.empty()) {
        while (root) { st.push(root); root = root->left; }
        root = st.top(); st.pop();
        if (--k == 0) return root->val;
        root = root->right;
    }
    return -1;  // Should not reach here
}
// TC: O(H + k) | SC: O(H)

Delete Node in BST β€” LC 450

Three cases:

  1. Leaf β†’ remove
  2. One child β†’ replace with child
  3. Two children β†’ replace with inorder successor (smallest in right subtree)
TreeNode* deleteNode(TreeNode* root, int key) {
    if (!root) return nullptr;
    if (key < root->val) root->left = deleteNode(root->left, key);
    else if (key > root->val) root->right = deleteNode(root->right, key);
    else {
        if (!root->left) return root->right;
        if (!root->right) return root->left;
        // Two children: find inorder successor
        TreeNode* succ = root->right;
        while (succ->left) succ = succ->left;
        root->val = succ->val;
        root->right = deleteNode(root->right, succ->val);
    }
    return root;
}

Insert into BST β€” LC 701

TreeNode* insertIntoBST(TreeNode* root, int val) {
    if (!root) return new TreeNode(val);
    if (val < root->val) root->left = insertIntoBST(root->left, val);
    else root->right = insertIntoBST(root->right, val);
    return root;
}

Recover BST β€” LC 99

During inorder, if prev->val > curr->val, we found a violation.

  • First violation β†’ first = prev, second = curr
  • Second violation β†’ update second = curr
TreeNode *first = nullptr, *second = nullptr, *prev = nullptr;
 
void recoverTree(TreeNode* root) {
    inorder(root);
    swap(first->val, second->val);
}
 
void inorder(TreeNode* root) {
    if (!root) return;
    inorder(root->left);
    if (prev && prev->val > root->val) {
        if (!first) first = prev;
        second = root;
    }
    prev = root;
    inorder(root->right);
}

1️⃣1️⃣ Tree Construction

From Preorder & Inorder β€” LC 105 πŸ”₯

Preorder: [3, 9, 20, 15, 7]    Inorder: [9, 3, 15, 20, 7]
           ↑ root                        ← left | right β†’

preorder[0] = 3 = root
In inorder: everything left of 3 = left subtree, right of 3 = right subtree
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    unordered_map<int, int> inMap;
    for (int i = 0; i < inorder.size(); i++)
        inMap[inorder[i]] = i;
 
    int preIdx = 0;
    return build(preorder, preIdx, 0, inorder.size() - 1, inMap);
}
 
TreeNode* build(vector<int>& preorder, int& preIdx, int inLeft, int inRight,
                unordered_map<int, int>& inMap) {
    if (inLeft > inRight) return nullptr;
 
    TreeNode* root = new TreeNode(preorder[preIdx++]);
    int inIdx = inMap[root->val];
 
    root->left  = build(preorder, preIdx, inLeft, inIdx - 1, inMap);
    root->right = build(preorder, preIdx, inIdx + 1, inRight, inMap);
    return root;
}
// TC: O(N) | SC: O(N) for hashmap

From Inorder & Postorder β€” LC 106

Same idea: postorder[last] = root. Build right subtree first (postorder goes right-to-left).

TreeNode* build(vector<int>& postorder, int& postIdx, int inLeft, int inRight,
                unordered_map<int, int>& inMap) {
    if (inLeft > inRight) return nullptr;
 
    TreeNode* root = new TreeNode(postorder[postIdx--]);
    int inIdx = inMap[root->val];
 
    root->right = build(postorder, postIdx, inIdx + 1, inRight, inMap);  // Right first!
    root->left  = build(postorder, postIdx, inLeft, inIdx - 1, inMap);
    return root;
}

Sorted Array to BST β€” LC 108

Pick the middle element as root β†’ guarantees balanced tree.

TreeNode* sortedArrayToBST(vector<int>& nums) {
    return build(nums, 0, nums.size() - 1);
}
 
TreeNode* build(vector<int>& nums, int left, int right) {
    if (left > right) return nullptr;
    int mid = left + (right - left) / 2;
    TreeNode* root = new TreeNode(nums[mid]);
    root->left  = build(nums, left, mid - 1);
    root->right = build(nums, mid + 1, right);
    return root;
}

BST from Preorder β€” LC 1008

Use upper bound approach β€” O(N):

TreeNode* bstFromPreorder(vector<int>& preorder) {
    int idx = 0;
    return build(preorder, idx, INT_MAX);
}
 
TreeNode* build(vector<int>& preorder, int& idx, int bound) {
    if (idx == preorder.size() || preorder[idx] > bound) return nullptr;
    TreeNode* root = new TreeNode(preorder[idx++]);
    root->left  = build(preorder, idx, root->val);
    root->right = build(preorder, idx, bound);
    return root;
}

1️⃣2️⃣ Serialize & Deserialize

Binary Tree β€” LC 297 πŸ’€

Use preorder with null markers ("#"):

// Serialize: preorder with null markers
string serialize(TreeNode* root) {
    if (!root) return "#";
    return to_string(root->val) + "," + serialize(root->left) + "," + serialize(root->right);
}
 
// Deserialize: read tokens from queue
TreeNode* deserialize(string data) {
    queue<string> tokens;
    stringstream ss(data);
    string token;
    while (getline(ss, token, ',')) tokens.push(token);
    return buildTree(tokens);
}
 
TreeNode* buildTree(queue<string>& tokens) {
    string val = tokens.front(); tokens.pop();
    if (val == "#") return nullptr;
    TreeNode* node = new TreeNode(stoi(val));
    node->left  = buildTree(tokens);
    node->right = buildTree(tokens);
    return node;
}

1️⃣3️⃣ Distance & Relation Problems

All Nodes Distance K β€” LC 863 πŸ”₯

Build a parent map, then BFS from target node for k levels:

vector<int> distanceK(TreeNode* root, TreeNode* target, int k) {
    unordered_map<TreeNode*, TreeNode*> parent;
    buildParentMap(root, nullptr, parent);
 
    queue<TreeNode*> q;
    unordered_set<TreeNode*> visited;
    q.push(target);
    visited.insert(target);
 
    while (!q.empty() && k > 0) {
        int sz = q.size();
        while (sz--) {
            auto curr = q.front(); q.pop();
            // Go left, right, AND parent
            for (auto next : {curr->left, curr->right, parent[curr]}) {
                if (next && !visited.count(next)) {
                    visited.insert(next);
                    q.push(next);
                }
            }
        }
        k--;
    }
 
    vector<int> ans;
    while (!q.empty()) { ans.push_back(q.front()->val); q.pop(); }
    return ans;
}
 
void buildParentMap(TreeNode* root, TreeNode* par,
                    unordered_map<TreeNode*, TreeNode*>& parent) {
    if (!root) return;
    parent[root] = par;
    buildParentMap(root->left, root, parent);
    buildParentMap(root->right, root, parent);
}

The Parent Map Trick

  • Trees only have downward pointers. To traverse upward, build a parent map first.
  • Then BFS works in all 3 directions: left, right, parent β€” like a graph.

🧠 Master Cheatsheet

The Decision Framework

What does the problem ask?
β”‚
β”œβ”€β”€ "Traverse the tree"
β”‚   β”œβ”€β”€ Level-by-level?  β†’ BFS with queue
β”‚   └── Path / subtree?  β†’ DFS (recursion or stack)
β”‚
β”œβ”€β”€ "Tree property" (depth, balanced, symmetric, same)
β”‚   └── DFS β€” compute bottom-up, return value from recursion
β”‚
β”œβ”€β”€ "Side view" (left, right, top, bottom)
β”‚   β”œβ”€β”€ Left/Right β†’ BFS (first/last of level) or DFS (depth tracking)
β”‚   └── Top/Bottom β†’ BFS with Horizontal Distance + ordered map
β”‚
β”œβ”€β”€ "Path sum" / "root to leaf"
β”‚   β”œβ”€β”€ Single path exists? β†’ DFS with target subtraction
β”‚   β”œβ”€β”€ All paths?          β†’ DFS + backtracking
β”‚   └── Any-to-any path?   β†’ Prefix sum + hashmap on tree
β”‚
β”œβ”€β”€ "Lowest Common Ancestor"
β”‚   β”œβ”€β”€ Binary Tree β†’ Recursive: left/right both found = root is LCA
β”‚   └── BST β†’ Use BST property to pick direction
β”‚
β”œβ”€β”€ "Construct tree from traversals"
β”‚   └── Find root from pre/postorder β†’ split using inorder position
β”‚
β”œβ”€β”€ "BST operation" (validate, insert, delete, kth)
β”‚   └── Use BST property: left < root < right
β”‚       └── Inorder = sorted β†’ kth smallest, validate, recover
β”‚
└── "Serialize / distance / misc"
    └── Serialize: preorder + null markers
    └── Distance K: parent map + BFS

Complexity Reference

OperationTimeSpace
Any single traversal
Level-order BFS ≀
BST Search / Insert / Delete
Balanced BST operations
Build tree from traversals
Serialize / Deserialize

Where = height ( for balanced, for skewed), = max width of tree.

Pattern Recognition Table

Signal in problemPattern to use
”Level order” / β€œby level” / β€œzigzag”BFS with queue, level-by-level template
”Depth” / β€œheight” / β€œbalanced”DFS, bottom-up return
”Same” / β€œsymmetric” / β€œmirror”DFS, compare two trees simultaneously
”Path sum” / β€œroot to leaf”DFS with target subtraction
”Maximum path sum” (any to any)DFS, max(0, child) + global max
”Path sum III” (any node downward)Prefix sum + hashmap
”Right/left side view”BFS (first/last of level) or DFS with depth
”Top/bottom view”BFS + horizontal distance + ordered map
”Boundary traversal”Left boundary + leaves + right boundary
”LCA”Recursive check left/right subtrees
”Validate BST”DFS with (min, max) range
”Kth smallest in BST”Iterative inorder, count to k
”Construct from preorder + inorder”Preorder[0] = root, split by inorder index
”Serialize / deserialize”Preorder with null markers + queue
”Distance K” / β€œall nodes at distance”Parent map + BFS
”Diameter”At each node: leftH + rightH, track global

Common Gotchas

1. minDepth: if one child is null, DON'T return 0 β€” go to the other child
2. BST validation: use long (LONG_MIN/MAX) to handle INT_MIN/INT_MAX edge cases
3. Path Sum: check at LEAF (both children null), not just any null node
4. LCA: return root when root == p || root == q (even before checking children)
5. Postorder iterative: the 1-stack version needs careful backtracking logic
6. Tree construction: preIdx must be passed by REFERENCE (not value)
7. Side views: DFS needs level tracking; BFS needs level-by-level snapshot
8. Prefix sum on tree: MUST backtrack the map when returning from subtree
9. Max Path Sum: return one branch only (can't fork), but update global with both
10. Diameter: it's EDGES not NODES β€” don't add 1 to leftH + rightH

1 item under this folder.