Interview Setbook · Part 3 of 6 · Q071–Q105
C++ Mastery Series · Interview Prep

The Interview
Problem Setbook

Part 3 of 6. Trees, BST and Heaps — the data structures that separate good programmers from great ones. Tree recursion is the single most testable skill in FAANG interviews. Master this part and your interview pass rate doubles.

Part 3 · Q071–Q105 Binary Trees Binary Search Trees Tree DP Heaps & Priority Queues
Chapter 07 · Q071–Q088 · Binary Trees
Binary Trees — Recursion Mastery, Traversals & Tree DP
The Tree Recursion Mental Model — One Thought Unlocks All

Assume your function already works for the left and right subtrees. Given those results, what do you do at the current node? That's tree recursion in one sentence. Every tree problem follows: base case (null = 0 or false or empty) → recurse left → recurse right → combine at current node. The art is knowing what to return upward and what to track globally.

Q071 · EASY Maximum Depth of Binary Tree
TreeRecursion
LeetCode #104
Must KnowRecursion Foundation

Return the maximum depth (number of nodes along longest root-to-leaf path) of a binary tree.

Tree 3 / \ 9 20 / \ 15 7
Output3
Decode — Depth at Node = 1 + Max(Depth Left, Depth Right)

Trust the recursion: assume maxDepth(left) and maxDepth(right) return correct depths. The current node adds 1 level. Base case: null node = depth 0.

Tree: 3 → [9, 20→[15,7]] maxDepth(null) = 0 maxDepth(9): 1 + max(0,0) = 1 maxDepth(15): 1 + max(0,0) = 1 maxDepth(7): 1 + max(0,0) = 1 maxDepth(20): 1 + max(1,1) = 2 maxDepth(3): 1 + max(1,2) = 3
C++ — Recursive and BFS (both shown) // Recursive DFS — clean, natural int maxDepth(TreeNode* root) { if(!root) return 0; return 1 + max(maxDepth(root->left), maxDepth(root->right)); } // BFS — counts levels (useful when recursion depth is a concern) int maxDepthBFS(TreeNode* root) { if(!root) return 0; queue<TreeNode*> q; q.push(root); int depth=0; while(!q.empty()){ depth++; for(int i=q.size(); i>0; i--){ TreeNode* n=q.front(); q.pop(); if(n->left) q.push(n->left); if(n->right) q.push(n->right); } } return depth; }
TimeO(n)
SpaceO(h) DFS · O(w) BFS
Q072 · EASY Invert Binary Tree
TreeRecursion
LeetCode #226
Must KnowRecursion Intuition Builder

Invert (mirror) a binary tree. Return the root of the inverted tree.

Input 4 / \ 2 7 /\ /\ 1 3 6 9
Output 4 / \ 7 2 /\ /\ 9 6 3 1
Decode — Swap Children, Then Recursively Invert Each

At each node: swap its left and right children. Then recursively invert the left subtree and the right subtree. Base case: null node → return null. This is three lines of code — any more and you're overthinking it.

C++ Solution — O(n) time · O(h) space TreeNode* invertTree(TreeNode* root) { if(!root) return nullptr; swap(root->left, root->right); // Swap children at current node invertTree(root->left); // Invert left subtree invertTree(root->right); // Invert right subtree return root; }
TimeO(n)
SpaceO(h)
Q073 · EASY Same Tree / Symmetric Tree
TreeRecursion
LeetCode #100 & #101
Must KnowStructural Comparison Pattern

Same Tree: Are two trees structurally identical with same node values?
Symmetric Tree: Is a single tree a mirror of itself around its centre?

Key Insight — Symmetric = Left's Left Matches Right's Right

For Same Tree: both null → true. One null → false. Values differ → false. Else recurse both children.
For Symmetric: helper isMirror(left, right). Both null → true. One null → false. Values equal AND isMirror(left.left, right.right) AND isMirror(left.right, right.left).

C++ — Both Problems // Same Tree bool isSameTree(TreeNode* p, TreeNode* q){ if(!p && !q) return true; if(!p || !q || p->val!=q->val) return false; return isSameTree(p->left,q->left) && isSameTree(p->right,q->right); } // Symmetric Tree bool isMirror(TreeNode* L, TreeNode* R){ if(!L && !R) return true; if(!L || !R || L->val!=R->val) return false; return isMirror(L->left,R->right) && isMirror(L->right,R->left); } bool isSymmetric(TreeNode* root){ return !root || isMirror(root->left, root->right); }
TimeO(n)
SpaceO(h)
Q074 · EASY Binary Tree Level Order Traversal
TreeBFS
LeetCode #102
Must KnowBFS Level-by-Level Template

Return all node values level by level (left to right) as a vector of vectors.

Decode — BFS with Level-Size Snapshot

Use a queue. At the start of each level, snapshot the queue size. Process exactly that many nodes (one full level). Push their children. Repeat. The size snapshot prevents mixing levels.

Tree: 3 / [9, 20 / [15,7]] Level 0: queue=[3], size=1 → process 3 → push 9,20 output: [3] Level 1: queue=[9,20], size=2 → process 9(no children), 20(push 15,7) output: [9,20] Level 2: queue=[15,7], size=2 → process both output: [15,7] Result: [[3],[9,20],[15,7]]
C++ Solution — O(n) time · O(w) space vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> res; if(!root) return res; queue<TreeNode*> q; q.push(root); while(!q.empty()){ int sz = q.size(); // Snapshot current level size res.emplace_back(); while(sz--){ TreeNode* n = q.front(); q.pop(); res.back().push_back(n->val); if(n->left) q.push(n->left); if(n->right) q.push(n->right); } } return res; } // Variants: Zigzag (LC#103) — alternate push direction per level // Right side view (LC#199) — take last element of each level
TimeO(n)
SpaceO(w) max width
Q075 · EASY Path Sum
TreeRecursion
LeetCode #112
Must KnowSubtract as You Go Down

Return true if the tree has a root-to-leaf path whose node values sum to targetSum.

Inputtarget=22 5→[4→[11→[7,2]], 8→[13,4→[,1]]]
Outputtrue (5+4+11+2)
Decode — Subtract Node Value, Check if Leaf with Remaining = 0

At each node: subtract its value from the target. At a leaf: check if remaining == 0. Pass the reduced target down. This is cleaner than accumulating a sum because you don't need to check if you're at a leaf separately when summing.

C++ Solution — O(n) bool hasPathSum(TreeNode* root, int target) { if(!root) return false; target -= root->val; if(!root->left && !root->right) return target == 0; // Leaf check return hasPathSum(root->left, target) || hasPathSum(root->right, target); } // Path Sum II — return all root-to-leaf paths with sum == target void dfs(TreeNode* n, int rem, vector<int>& path, vector<vector<int>>& res){ if(!n) return; path.push_back(n->val); rem-=n->val; if(!n->left&&!n->right&&rem==0) res.push_back(path); dfs(n->left,rem,path,res); dfs(n->right,rem,path,res); path.pop_back(); // Backtrack }
TimeO(n)
SpaceO(h)
Q076 · MEDIUM Diameter of Binary Tree
TreeTree DP
LeetCode #543
Must KnowDiameter ≠ Just Depth — Track Both

Find the diameter of a binary tree — the longest path between any two nodes (may not pass through root). Return the number of edges.

Tree 1 / \ 2 3 /\ 4 5
Output3
Path[4,2,1,3] or [5,2,1,3]
Decode — At Each Node: Diameter Through It = Left Height + Right Height

The longest path through any node uses the tallest path in its left subtree PLUS the tallest path in its right subtree. DFS returns height upward while updating a global maximum with (left_height + right_height) at each node. These are two different things — the return value (height) and the side-effect (update max diameter).

Tree: 1/[2/[4,5], 3] height(4) = 0 → update diameter: 0+0=0 height(5) = 0 → update diameter: 0+0=0 height(2) = 1 → update diameter: 1+1=2 (paths through 2) height(3) = 0 → update diameter: 0+0=0 height(1) = 2 → update diameter: 2+1=3 (paths through 1: 4→2→1→3) Global max = 3
C++ Solution — O(n) single DFS pass int diameterOfBinaryTree(TreeNode* root) { int diameter = 0; function<int(TreeNode*)> height = [&](TreeNode* node) -> int { if(!node) return 0; int L = height(node->left); int R = height(node->right); diameter = max(diameter, L + R); // Update global max return 1 + max(L, R); // Return height upward }; height(root); return diameter; }
TimeO(n)
SpaceO(h)
PatternSame "return height, update global" in: Max Path Sum, Balanced Check
Q077 · HARD Binary Tree Maximum Path Sum
TreeTree DP
LeetCode #124
Must KnowFAANG FavouritePath Through Node vs Path Returned Up

Find the maximum path sum in a binary tree. A path is any sequence of nodes where each pair of adjacent nodes has an edge. The path does not need to pass through the root. Nodes can be negative.

Input[-10,9,20,null,null,15,7]
Output42 (15+20+7)
Decode — Two Different Computations at Each Node

What to update globally: node.val + max(0, leftGain) + max(0, rightGain). This is the max path that passes through this node as the "peak".
What to return upward: node.val + max(0, max(leftGain, rightGain)). You can only continue the path through ONE branch going upward — you can't split.
Clamp negative gains to 0: a negative subtree only hurts, so don't include it.

[-10, 9, 20/[15,7]] gain(9): L=0,R=0. update=9+0+0=9. return 9+max(0,0)=9 gain(15): L=0,R=0. update=15. return 15 gain(7): L=0,R=0. update=7. return 7 gain(20): L=15,R=7. update = 20+15+7 = 42 ← global max return = 20+max(15,7) = 35 gain(-10):L=9,R=35. update = -10+9+35=34 (< 42, no update) return = -10+max(9,35)=25 Answer: 42
C++ Solution — O(n) single DFS pass int maxPathSum(TreeNode* root) { int best = INT_MIN; function<int(TreeNode*)> gain = [&](TreeNode* n) -> int { if(!n) return 0; int L = max(0, gain(n->left)); // Clamp: negative branch = skip int R = max(0, gain(n->right)); best = max(best, n->val + L + R); // Path through this node as peak return n->val + max(L, R); // Best single branch going up }; gain(root); return best; }
TimeO(n)
SpaceO(h)
PatternIdentical structure to Diameter — "return up one branch, update globally"
Q078 · MEDIUM Lowest Common Ancestor of Binary Tree
TreeRecursion
LeetCode #236
Must KnowAsked 5,000+Post-Order Search

Find the Lowest Common Ancestor (LCA) of two nodes p and q in a binary tree (not necessarily a BST).

Inputroot=[3,5,1,6,2,0,8] p=5, q=1
Output3
Input 2p=5, q=4
Output 25
Decode — If Both Left and Right Return Non-Null, Current Node is LCA

If the current node is p or q, return it. Recurse left and right. If both return non-null → p and q are in different subtrees → current node is the LCA. If only one returns non-null → both are in that subtree → propagate the found node upward.

p=5, q=4. Tree: 3/[5/[6,2/[7,4]], 1/[0,8]] lca(6,5,4): 6≠p,q. lca(L=null)=null, lca(R=null)=null → return null lca(7,5,4): null→null lca(4,5,4): 4==q → return 4 lca(2,5,4): L=null(7 side), R=4 → return 4 (propagate up) lca(5,5,4): 5==p → return 5 (don't recurse further) lca(3,5,4): L=5, R=null(1 side has no p or q) Both non-null? NO. Only L is non-null → return 5 LCA(5,4) = 5 (5 is ancestor of 4) ✓
C++ Solution — O(n) time · O(h) space TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if(!root || root==p || root==q) return root; // Found p or q (or null) TreeNode* L = lowestCommonAncestor(root->left, p, q); TreeNode* R = lowestCommonAncestor(root->right, p, q); if(L && R) return root; // p and q in different subtrees → current is LCA return L ? L : R; // Both in one subtree → propagate that side }
TimeO(n)
SpaceO(h)
Q079 · MEDIUM Binary Tree Right Side View
TreeBFS
LeetCode #199
Must KnowLast Node of Each BFS Level

Return the values of the nodes visible when standing to the right of the tree (rightmost node of each level).

Tree 1 / \ 2 3 \ \ 5 4
Output[1,3,4]
Decode — BFS Level Order, Take Last Element Each Level

Level-order BFS (see Q074). At the end of processing each level, the last node processed is the rightmost visible node. Push it to result. Can also do DFS: visit right child first, and include only the first node encountered at each depth.

C++ — BFS and DFS approaches // BFS — take last element of each level vector<int> rightSideView(TreeNode* root){ vector<int> res; if(!root) return res; queue<TreeNode*> q; q.push(root); while(!q.empty()){ int sz=q.size(); while(sz--){ TreeNode* n=q.front(); q.pop(); if(sz==0) res.push_back(n->val); // Last of level → visible if(n->left) q.push(n->left); if(n->right) q.push(n->right); } } return res; } // DFS — visit right first, record first node at each depth void dfs(TreeNode* n, int depth, vector<int>& res){ if(!n) return; if(depth==(int)res.size()) res.push_back(n->val); dfs(n->right, depth+1, res); dfs(n->left, depth+1, res); }
TimeO(n)
SpaceO(w) BFS · O(h) DFS
Q080 · MEDIUM Count Good Nodes in Binary Tree
TreeDFS
LeetCode #1448
Pass Max-So-Far Down the Path

A "good node" is a node where no ancestor has a value greater than it. Count all good nodes.

Decode — Pass the Maximum Seen So Far as a Parameter

DFS with an extra parameter: maxSoFar = the maximum value on the path from root to current node. A node is "good" if node.val >= maxSoFar. Update maxSoFar = max(maxSoFar, node.val) before recursing.

C++ Solution — O(n) int goodNodes(TreeNode* root){ int count=0; function<void(TreeNode*,int)> dfs=[&](TreeNode* n, int mx){ if(!n) return; if(n->val >= mx) count++; // Good node dfs(n->left, max(mx, n->val)); dfs(n->right, max(mx, n->val)); }; dfs(root, INT_MIN); return count; }
TimeO(n)
SpaceO(h)
Q081 · MEDIUM Balanced Binary Tree Check
TreeTree DP
LeetCode #110
Must KnowReturn -1 to Propagate Imbalance

Check if a binary tree is height-balanced. A tree is balanced if for every node, the heights of left and right subtrees differ by at most 1.

Decode — Return Height or -1 (Sentinel for Imbalanced)

The naive approach calls height() for every node — O(n²). Better: DFS returns height, but returns -1 if ANY subtree is unbalanced. Once -1 propagates up, all ancestors short-circuit. Total: O(n).

C++ Solution — O(n) one-pass int checkHeight(TreeNode* n){ if(!n) return 0; int L = checkHeight(n->left); if(L==-1) return -1; // Propagate imbalance upward int R = checkHeight(n->right); if(R==-1) return -1; if(abs(L-R)>1) return -1; // Current node is unbalanced return 1+max(L,R); } bool isBalanced(TreeNode* root){ return checkHeight(root) != -1; }
TimeO(n)
SpaceO(h)
Q082 · MEDIUM Construct Binary Tree from Preorder and Inorder
TreeHash
LeetCode #105
Must KnowPreorder[0] = Root Always

Construct a binary tree from its preorder and inorder traversal arrays.

Preorder[3,9,20,15,7]
Inorder[9,3,15,20,7]
Tree 3 / \ 9 20 /\ 15 7
Decode — Preorder Gives Root; Inorder Splits Left and Right Subtrees

Preorder's first element is always the root. Find it in inorder — everything to its left is the left subtree, everything to its right is the right subtree. Count tells you how many preorder elements belong to each subtree. Recurse. Use a hash map for O(1) inorder lookups.

pre=[3,9,20,15,7] in=[9,3,15,20,7] root=pre[0]=3, pos=inorder[3]=1 left subtree: inorder[0..0]=[9] preorder[1..1]=[9] right subtree: inorder[2..4]=[15,20,7] preorder[2..4]=[20,15,7] Build(left): root=9, pos=0 → leaf Build(right): root=20, pos=in[20]=3 left: in[2..2]=[15], pre[3..3]=[15] → leaf right: in[4..4]=[7], pre[4..4]=[7] → leaf Result: correct tree ✓
C++ Solution — O(n) with hash map TreeNode* buildTree(vector<int>& pre, vector<int>& in){ unordered_map<int,int> pos; for(int i=0;i<(int)in.size();i++) pos[in[i]]=i; int idx=0; function<TreeNode*(int,int)> build=[&](int l, int r) -> TreeNode*{ if(l>r) return nullptr; int rootVal = pre[idx++]; TreeNode* node = new TreeNode(rootVal); int mid = pos[rootVal]; node->left = build(l, mid-1); // Build left BEFORE right (preorder idx) node->right = build(mid+1, r); return node; }; return build(0, in.size()-1); }
TimeO(n)
SpaceO(n)
SimilarLC#106 Inorder+Postorder — same idea, postorder reads from right
Q083 · MEDIUM Serialize and Deserialize Binary Tree
TreeDesign
LeetCode #297
Must KnowPreorder with Null Markers

Design an algorithm to serialize a binary tree to a string and deserialize it back. The encoded string must be decodable to the original tree structure.

Decode — Preorder DFS with Null Markers Uniquely Encodes Any Tree

Preorder DFS: write node values with a separator. Write "N" for nulls. This uniquely determines the structure because the null markers tell us exactly where subtrees end. Deserialize by reading tokens in preorder and recursively building.

C++ Solution — O(n) encode and decode string serialize(TreeNode* root){ if(!root) return "N,"; return to_string(root->val)+","+serialize(root->left)+serialize(root->right); } TreeNode* deserialize(string data){ queue<string> q; string tok; for(char c : data){ if(c==','){q.push(tok); tok="";} else tok+=c; } function<TreeNode*()> build=[&]() -> TreeNode*{ string v=q.front(); q.pop(); if(v=="N") return nullptr; TreeNode* node=new TreeNode(stoi(v)); node->left = build(); node->right = build(); return node; }; return build(); }
TimeO(n)
SpaceO(n)
Q084 · MEDIUM Sum Root to Leaf Numbers
TreeDFS
LeetCode #129
Pass Running Number Down (×10 + digit)

Each root-to-leaf path forms a number (e.g. path 1→2→3 = 123). Sum all such numbers.

Tree 4 / \ 9 0 /\ 5 1
Output1026
Why495+491+40=1026
C++ Solution — O(n) int sumNumbers(TreeNode* root){ function<int(TreeNode*,int)> dfs=[&](TreeNode* n, int num)->int{ if(!n) return 0; num = num*10 + n->val; // Extend the number if(!n->left && !n->right) return num; // Leaf: contribute this number return dfs(n->left,num) + dfs(n->right,num); }; return dfs(root,0); }
TimeO(n)
SpaceO(h)
Q085 · MEDIUM Binary Tree Zigzag Level Order
TreeBFS
LeetCode #103
Toggle Direction Per Level

Level-order traversal but alternate left-to-right and right-to-left per level.

C++ — BFS with direction toggle vector<vector<int>> zigzagLevelOrder(TreeNode* root){ vector<vector<int>> res; if(!root) return res; queue<TreeNode*> q; q.push(root); bool leftToRight=true; while(!q.empty()){ int sz=q.size(); vector<int> level(sz); for(int i=0;i<sz;i++){ TreeNode* n=q.front();q.pop(); int pos=leftToRight ? i : sz-1-i; // Fill from correct direction level[pos]=n->val; if(n->left) q.push(n->left); if(n->right) q.push(n->right); } res.push_back(level); leftToRight=!leftToRight; } return res; }
TimeO(n)
SpaceO(w)
Q086 · MEDIUM Flatten Binary Tree to Linked List
TreeRecursion
LeetCode #114
Must KnowMorris-like: Connect in Preorder

Flatten a binary tree in-place to a linked list in preorder. Use right pointers, set all left pointers to null.

Input1/[2/[3,4],5/[,6]]
Output1→2→3→4→5→6 (right ptrs)
Decode — For Each Node: Move Right Subtree After Rightmost of Left Subtree

For each node with a left child: find the rightmost node of the left subtree (it's the predecessor in preorder). Connect the right subtree to that node's right. Move the left subtree to be the right child. Set left to null. Advance to right child and repeat.

C++ — O(n) O(1) iterative void flatten(TreeNode* root){ TreeNode* cur=root; while(cur){ if(cur->left){ // Find rightmost of left subtree TreeNode* pre=cur->left; while(pre->right) pre=pre->right; pre->right = cur->right; // Attach right subtree cur->right = cur->left; // Move left subtree to right cur->left = nullptr; } cur=cur->right; } }
TimeO(n)
SpaceO(1)
Q087 · MEDIUM Path Sum III (Any Path, Any Node)
TreePrefix Sum
LeetCode #437
Must KnowPrefix Sum on Tree Paths

Count paths in a binary tree that sum to a target. The path must go downward but doesn't need to start or end at root/leaf.

Decode — Prefix Sum HashMap on Root-to-Node Paths

Same as subarray sum equals k (Q021) but on a tree path. DFS with a hash map storing prefix sums. At each node: check if (currentSum - target) exists in the map → that many paths end here. Add currentSum to map, recurse, then remove it (backtrack). O(n) instead of O(n²).

C++ Solution — O(n) int pathSum(TreeNode* root, long long target){ unordered_map<long long,int> freq; freq[0]=1; // Empty prefix int count=0; function<void(TreeNode*,long long)> dfs=[&](TreeNode* n, long long psum){ if(!n) return; psum += n->val; count += freq[psum-target]; // Paths ending here with sum=target freq[psum]++; dfs(n->left, psum); dfs(n->right, psum); freq[psum]--; // Backtrack — remove this path }; dfs(root,0); return count; }
TimeO(n)
SpaceO(h)
Q088 · HARD Binary Tree Cameras
TreeGreedy
LeetCode #968
Must KnowGreedy: Place Cameras at Leaves' Parents

Place the minimum number of cameras on tree nodes. Each camera monitors its node, parent, and immediate children. Return the minimum cameras needed to monitor all nodes.

Decode — Greedy: Never Place Camera at Leaf; Place at Its Parent

Bottom-up DFS with three states: 0 = uncovered (needs a camera from parent), 1 = has camera, 2 = covered (by child camera). Leaves return 0. A node places a camera if any child is uncovered (state 0). If any child has a camera, this node is covered (state 2). Otherwise state 0. Root: if root is state 0, add one more camera.

C++ Solution — O(n) int cameras=0; int dfs(TreeNode* n){ // Returns: 0=uncovered, 1=has camera, 2=covered by child if(!n) return 2; // Null is "covered" (no need to monitor null) int L=dfs(n->left), R=dfs(n->right); if(L==0||R==0){ // A child is uncovered → place camera here cameras++; return 1; } if(L==1||R==1) return 2; // A child has camera → this node is covered return 0; // Neither child → this node is uncovered } int minCameraCover(TreeNode* root){ cameras=0; if(dfs(root)==0) cameras++; // Root uncovered → add camera return cameras; }
TimeO(n)
SpaceO(h)
↑ back to top
Chapter 08 · Q089–Q097 · Binary Search Trees
Binary Search Trees — BST Property, Inorder = Sorted, Range Queries
BST Key Properties — Memorise These

Property: left < node < right at every node. This is a global constraint, not just parent-child.
Inorder traversal of BST = sorted array. This is the single most useful BST fact. Any problem asking "k-th smallest", "sorted output", or "predecessor/successor" uses inorder.
Search, insert, delete: O(h) = O(log n) balanced, O(n) skewed. Always ask if the tree is balanced.
BST vs Binary Tree: In BST problems, USE the BST property to prune. If you're traversing every node, you're ignoring the BST structure.

Q089 · EASY Search in a BST
BST
LeetCode #700
FoundationUse BST Property to Prune

Find and return the subtree rooted at the node with value equal to the target. Return null if not found.

C++ — Recursive and Iterative // Recursive TreeNode* searchBST(TreeNode* root, int val){ if(!root || root->val==val) return root; return val < root->val ? searchBST(root->left,val) : searchBST(root->right,val); } // Iterative — O(h) O(1) TreeNode* searchBSTIter(TreeNode* root, int val){ while(root && root->val!=val) root = val < root->val ? root->left : root->right; return root; }
TimeO(h)
SpaceO(1) iterative
Q090 · MEDIUM Validate Binary Search Tree
BSTRecursion
LeetCode #98
Must KnowPass Valid Range — Not Just Compare with Parent

Given a binary tree, determine if it is a valid BST.

Decode — Each Node Has an Inherited Valid Range [lo, hi]

The value at each node must satisfy the constraints of ALL its ancestors, not just its direct parent. Pass a (lo, hi) range down the tree. Going left: hi = parent's value. Going right: lo = parent's value. A node is valid only if lo < node.val < hi.

Tree: 5/[1, 4/[3,6]] (invalid — 4<5 but 3<4 yet 3 is in right subtree of 5) validate(5, -INF, +INF): 5 in range ✓ validate(1, -INF, 5): 1 in range ✓ → leaf ✓ validate(4, 5, +INF): 4 < lo=5 → INVALID ✗ Result: false ✓
C++ Solution — O(n) bool isValid(TreeNode* n, long lo, long hi){ if(!n) return true; if(n->val <= lo || n->val >= hi) return false; // Out of valid range return isValid(n->left, lo, n->val) && isValid(n->right, n->val, hi); } bool isValidBST(TreeNode* root){ return isValid(root, LONG_MIN, LONG_MAX); }
TimeO(n)
SpaceO(h)
Q091 · EASY Kth Smallest Element in BST
BSTInorder
LeetCode #230
Must KnowInorder BST = Sorted Array

Find the kth smallest element in a BST in O(h+k) time.

Decode — Inorder Traversal Gives Sorted Order — Count to k

Inorder traversal of a BST visits nodes in ascending order. The kth node visited in inorder is the kth smallest. Use iterative inorder with an explicit stack to stop early at exactly k nodes — no need to traverse the whole tree.

C++ — Iterative Inorder (stops at k) int kthSmallest(TreeNode* root, int k){ stack<TreeNode*> st; TreeNode* cur=root; while(cur || !st.empty()){ while(cur){ st.push(cur); cur=cur->left; } // Go all the way left cur=st.top(); st.pop(); if(--k==0) return cur->val; // kth node in inorder cur=cur->right; } return -1; }
TimeO(h+k)
SpaceO(h)
Q092 · MEDIUM Lowest Common Ancestor of BST
BST
LeetCode #235
Must KnowUse BST Property — No Full Traversal Needed

Find LCA of p and q in a BST. Unlike a general binary tree, use the BST property to guide the search.

Decode — If Both p,q < Node → Go Left. Both > → Go Right. Else → Here.

If both p and q are smaller than the current node, the LCA must be in the left subtree. If both are larger, the LCA is in the right subtree. Otherwise, the current node is where they diverge — it IS the LCA. O(h) — no need to explore both subtrees.

C++ — Iterative O(h) O(1) TreeNode* lowestCommonAncestorBST(TreeNode* root, TreeNode* p, TreeNode* q){ while(root){ if(p->val < root->val && q->val < root->val) root=root->left; // Both left else if(p->val > root->val && q->val > root->val) root=root->right; // Both right else return root; // Split here — LCA found } return nullptr; }
TimeO(h)
SpaceO(1)
Q093 · MEDIUM Insert and Delete in BST
BST
LeetCode #701 & #450
Must KnowDelete: Successor/Predecessor Replacement

Insert: Insert a value into a BST, return the new root.
Delete: Delete a node with given key from a BST. Three cases to handle.

Delete Has 3 Cases — Master All Three

Case 1: Leaf node → just delete (return null).
Case 2: One child → replace with that child.
Case 3: Two children → replace value with inorder successor (smallest in right subtree), then delete the successor from right subtree.

C++ — Insert and Delete // Insert — BST search to find position, then create node 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; } // Delete — handle 3 cases 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 { // Found node to delete if(!root->left) return root->right; // Case 1&2: no left child if(!root->right) return root->left; // Case 1&2: no right child // Case 3: find inorder successor (min of right subtree) TreeNode* succ=root->right; while(succ->left) succ=succ->left; root->val = succ->val; // Replace value with successor root->right = deleteNode(root->right, succ->val); // Delete successor } return root; }
TimeO(h)
SpaceO(h)
Q094 · MEDIUM Convert Sorted Array to BST
BSTRecursion
LeetCode #108
Always Pick Middle as Root for Height Balance

Convert a sorted array to a height-balanced BST.

C++ Solution — O(n) TreeNode* sortedArrayToBST(vector<int>& nums){ function<TreeNode*(int,int)> build=[&](int l,int r)->TreeNode*{ if(l>r) return nullptr; int mid=l+(r-l)/2; TreeNode* n=new TreeNode(nums[mid]); n->left =build(l,mid-1); n->right=build(mid+1,r); return n; }; return build(0,nums.size()-1); }
TimeO(n)
SpaceO(log n)
Q095 · MEDIUM BST Iterator
BSTDesign
LeetCode #173
Must KnowControlled Inorder — Push Left Path on Demand

Implement a BST iterator with next() returning next smallest and hasNext(). Both must run in O(1) average with O(h) space.

Decode — Lazy Inorder: Only Process What You Need

Maintain an explicit stack. Constructor: push all leftmost nodes from root. next(): pop the top (that's the next smallest), then push all leftmost nodes of its right subtree. This is "paused inorder" — we only push nodes we'll visit next.

C++ Solution — O(1) avg per op · O(h) space class BSTIterator { stack<TreeNode*> st; void pushLeft(TreeNode* n){ while(n){st.push(n);n=n->left;} } public: BSTIterator(TreeNode* root){ pushLeft(root); } int next(){ TreeNode* top=st.top(); st.pop(); pushLeft(top->right); // Push left path of right subtree return top->val; } bool hasNext(){ return !st.empty(); } };
next/hasNextO(1) amortised
SpaceO(h)
Q096 · MEDIUM Recover Binary Search Tree (Two Swapped Nodes)
BSTInorder
LeetCode #99
Must KnowFind Inorder Violation — Two Misplaced Nodes

Exactly two nodes in a BST are swapped by mistake. Recover the tree by fixing the two nodes without changing structure.

Decode — Inorder Should Be Sorted — Find the Two Violations

In inorder traversal of a valid BST, values are strictly increasing. The two swapped nodes create descents (where prev > current). There are either one or two descents. The first node to swap = first.prev at the first descent. The second = second.current at the last descent. Swap their values.

C++ Solution — O(n) inorder with O(1) extra space (Morris traversal possible) TreeNode *first=nullptr, *second=nullptr, *prev=nullptr; void inorder(TreeNode* n){ if(!n) return; inorder(n->left); if(prev && prev->val > n->val){ // Violation found if(!first) first=prev; // First violation: prev is the bad node second=n; // Always update second } prev=n; inorder(n->right); } void recoverTree(TreeNode* root){ first=second=prev=nullptr; inorder(root); swap(first->val, second->val); }
TimeO(n)
SpaceO(h) stack · O(1) with Morris
Q097 · MEDIUM Unique BSTs (Catalan Numbers)
BSTDP
LeetCode #96
DP: Root i → (i-1) left choices × (n-i) right choices

Count the number of structurally unique BSTs that can store values 1 to n.

n=3Output: 5
n=1Output: 1
Decode — G(n) = Sum of G(i-1) × G(n-i) for i = 1 to n

If we pick i as the root, the left subtree has (i-1) nodes and the right has (n-i) nodes. The number of combinations = G(i-1) × G(n-i). Sum over all root choices. This is the Catalan number recurrence: G(0)=G(1)=1.

C++ — DP Tabulation O(n²) · O(n) int numTrees(int n){ vector<int> G(n+1,0); G[0]=G[1]=1; for(int i=2;i<=n;i++) for(int j=1;j<=i;j++) G[i]+=G[j-1]*G[i-j]; // j is the root return G[n]; } // Formula: C(n) = C(2n,n) / (n+1) (nth Catalan number)
TimeO(n²)
SpaceO(n)
↑ back to top
Chapter 09 · Q098–Q105 · Heaps & Priority Queues
Heaps & Priority Queues — Always Get the Extreme in O(log n)
Heap Pattern Recognition — When to Think "Heap"

Signal 1 — "Kth largest/smallest": Min-heap of size k. If new element > top: pop, push. Final top = kth largest.
Signal 2 — "Stream of numbers": Two heaps (max + min) for running median. Min-heap for running minimum.
Signal 3 — "Merge k sorted": Push head of each list. Pop min, push next from same list. O(n log k).
Signal 4 — "Scheduling/Tasks": Priority queue ordered by deadline, frequency, or cost.
Signal 5 — Greedy + Ordering: Always processing the most/least extreme element? Heap is your structure.

Q098 · MEDIUM Kth Largest Element in an Array
ArrayHeap
LeetCode #215
Must KnowAsked 7,000+Min-Heap of Size k

Find the kth largest element in an unsorted array. Not the kth distinct element.

Input[3,2,1,5,6,4], k=2
Output5
Decode — Maintain a Min-Heap of Exactly k Largest Seen So Far

A min-heap of size k tracks the k largest elements. The heap top is the kth largest (the minimum of the k largest). For each new element: push it. If heap size exceeds k, pop the minimum. At the end, heap.top() = kth largest.

[3,2,1,5,6,4], k=2 push 3: heap=[3] size=1 ≤ 2 → ok push 2: heap=[2,3] size=2 → ok push 1: heap=[2,3,1] size=3 > 2 → pop min=1. heap=[2,3] push 5: heap=[2,3,5] size=3 > 2 → pop min=2. heap=[3,5] push 6: heap=[3,5,6] size=3 > 2 → pop min=3. heap=[5,6] push 4: heap=[5,6,4] size=3 > 2 → pop min=4. heap=[5,6] heap.top() = 5 (2nd largest ✓)
C++ — Min-Heap O(n log k) and QuickSelect O(n) avg // Min-Heap — O(n log k) int findKthLargest(vector<int>& nums, int k){ priority_queue<int,vector<int>,greater<int>> minH; // Min-heap for(int x : nums){ minH.push(x); if((int)minH.size() > k) minH.pop(); } return minH.top(); } // QuickSelect — O(n) average (randomized pivot for worst-case safety) int quickSelect(vector<int>& a, int lo, int hi, int k){ int pivot=a[hi], i=lo-1; for(int j=lo;j<hi;j++) if(a[j]>=pivot) swap(a[++i],a[j]); swap(a[++i],a[hi]); if(i==k-1) return a[i]; return i > k-1 ? quickSelect(a,lo,i-1,k) : quickSelect(a,i+1,hi,k); }
HeapO(n log k)
QuickSelectO(n) avg
Q099 · HARD Find Median From Data Stream
Two HeapsDesign
LeetCode #295
Must KnowTwo-Heap Design — Classic

Design a structure supporting addNum(int) and findMedian(). addNum runs in O(log n), findMedian in O(1).

Decode — Max-Heap Holds Lower Half, Min-Heap Holds Upper Half

Two heaps partition the sorted array. maxH (lower half): max-heap, top = largest of the smaller half. minH (upper half): min-heap, top = smallest of the larger half. Keep them balanced: |size difference| ≤ 1. Median = top of larger heap (odd) or average of both tops (even).

Add: 1, 2, 3 addNum(1): maxH=[],minH=[]. Push to maxH → [1]. Rebalance: minH gets maxH.top()=1 → maxH=[], minH=[1] Whoops let's use the clean approach: Always push to maxH first. Then balance. addNum(1): maxH.push(1)=[1]. size diff: |0-1|=1 ok. median=minH empty→maxH.top()=1 Actually cleaner strategy: Push to maxH. Push maxH.top() to minH, pop from maxH. If minH larger: push minH.top to maxH, pop minH. addNum(1): maxH.push(1). push 1→minH, pop maxH. maxH=[],minH=[1]. minH bigger→push 1→maxH,pop minH. maxH=[1],minH=[] addNum(2): push 2→maxH=[2,1]. push 2→minH=[2],pop maxH=[1]. minH not bigger. maxH=[1],minH=[2] addNum(3): push 3→maxH=[3,1]. push 3→minH=[2,3],pop maxH=[1]. minH bigger→push 2→maxH=[2,1],pop minH=[3] maxH=[2,1], minH=[3] findMedian(): equal sizes → (maxH.top()+minH.top())/2 = (2+3)/2 = 2.5
C++ Solution — addNum O(log n) · findMedian O(1) class MedianFinder { priority_queue<int> maxH; // Lower half priority_queue<int,vector<int>,greater<int>> minH; // Upper half public: void addNum(int n){ maxH.push(n); minH.push(maxH.top()); maxH.pop(); // Always push top of lower to upper if(minH.size() > maxH.size()+1) // Rebalance if upper gets too big { maxH.push(minH.top()); minH.pop(); } } double findMedian(){ return minH.size() > maxH.size() ? minH.top() : (maxH.top() + minH.top()) / 2.0; } };
addNumO(log n)
findMedianO(1)
Q100 · HARD Merge K Sorted Lists (Heap)
HeapLinked List
LeetCode #23
Must KnowFAANG Favourite

Merge k sorted linked lists into one sorted list. (Revisited here from heap perspective.)

Heap Approach Complexity Analysis

n total nodes, k lists. Each node enters the heap once (O(log k)) and exits once (O(log k)). Total: O(n log k). Space: O(k) for the heap. Compare with naive pairwise merge: O(n log k) same time but O(1) space if done carefully.

C++ — Already shown in Q065. Revisit the complexity. // Reminder: min-heap stores (value, node*) pairs using P = pair<int,ListNode*>; priority_queue<P,vector<P>,greater<P>> pq; for(auto* l:lists) if(l) pq.push({l->val,l}); // ... (full solution in Q065) // ALTERNATIVE: Divide and Conquer pairwise merge // Time: O(n log k), Space: O(log k) recursion ListNode* mergeTwo(ListNode* a, ListNode* b){ ListNode d(0); ListNode* t=&d; while(a&&b){ if(a->val<=b->val){t->next=a;a=a->next;} else{t->next=b;b=b->next;} t=t->next; } t->next=a?a:b; return d.next; } ListNode* mergeKD(vector<ListNode*>& lists, int lo, int hi){ if(lo==hi) return lists[lo]; if(lo+1==hi) return mergeTwo(lists[lo],lists[hi]); int mid=lo+(hi-lo)/2; return mergeTwo(mergeKD(lists,lo,mid), mergeKD(lists,mid+1,hi)); }
TimeO(n log k)
SpaceO(k)
Q101 · HARD K Closest Points to Origin
ArrayHeap
LeetCode #973
Must KnowMax-Heap of Size k — Opposite of Kth Largest

Return the k closest points to the origin (0,0). Distance = sqrt(x²+y²) — but compare x²+y² to avoid sqrt.

Inputpts=[[1,3],[-2,2]] k=1
Output[[-2,2]]
Decode — Max-Heap of k Closest; Evict Farthest When Oversized

Use a max-heap by distance. Keep at most k elements. When a new point has distance less than the farthest in the heap (the max-heap top), pop the farthest and push the new point. Final heap contains the k closest.

C++ Solution — O(n log k) vector<vector<int>> kClosest(vector<vector<int>>& pts, int k){ // Max-heap by distance squared (farthest at top, evict when > k) using T=pair<int,int>; // {dist_sq, index} priority_queue<T> pq; // Max-heap for(int i=0;i<(int)pts.size();i++){ int d=pts[i][0]*pts[i][0]+pts[i][1]*pts[i][1]; pq.push({d,i}); if((int)pq.size()>k) pq.pop(); // Evict farthest } vector<vector<int>> res; while(!pq.empty()){ res.push_back(pts[pq.top().second]); pq.pop(); } return res; }
TimeO(n log k)
SpaceO(k)
AltQuickSelect: O(n) avg · Sort: O(n log n)
Q102 · MEDIUM Top K Frequent Words
HeapHash
LeetCode #692
Custom Comparator: Freq Desc, Lex Asc on Tie

Return the k most frequent words from a list. If tie in frequency, sort alphabetically.

Input["i","love","leetcode","i", "love","coding"], k=2
Output["i","love"]
C++ Solution — Custom Comparator Heap vector<string> topKFrequent(vector<string>& words, int k){ unordered_map<string,int> freq; for(auto& w:words) freq[w]++; // Min-heap: evict lowest-priority (least freq, lex largest) auto cmp=[&](const string& a, const string& b){ return freq[a]!=freq[b] ? freq[a]>freq[b] : a<b; // Min: lower freq or lex larger }; priority_queue<string,vector<string>,decltype(cmp)> pq(cmp); for(auto& [w,f]:freq){ pq.push(w); if((int)pq.size()>k) pq.pop(); } vector<string> res; while(!pq.empty()){ res.push_back(pq.top()); pq.pop(); } reverse(res.begin(),res.end()); return res; }
TimeO(n log k)
SpaceO(n+k)
Q103 · HARD Ugly Number II (Smallest Primes = 2, 3, 5 Only)
HeapDP
LeetCode #264
Min-Heap: Generate Multiples On Demand

Find the nth ugly number. Ugly numbers have only prime factors 2, 3, 5. Sequence: 1,2,3,4,5,6,8,9,10,12...

Decode — Heap Generates the Sequence in Order

Start with 1 in a min-heap. Each time you pop a number x, push x×2, x×3, x×5. Use a set to avoid duplicates. The nth pop is the nth ugly number.

C++ — Heap O(n log n) and DP O(n) approach // DP approach (optimal) int nthUglyNumber(int n){ vector<int> dp(n); dp[0]=1; int i2=0,i3=0,i5=0; for(int i=1;i<n;i++){ int m=min({dp[i2]*2, dp[i3]*3, dp[i5]*5}); dp[i]=m; if(m==dp[i2]*2) i2++; if(m==dp[i3]*3) i3++; // Use if (not else if) — multiple pointers may advance if(m==dp[i5]*5) i5++; } return dp[n-1]; }
DPO(n)
SpaceO(n)
Q104 · HARD IPO (Maximize Capital)
Two HeapsGreedy
LeetCode #502
Must KnowUnlock Projects by Capital, Greedily Pick Max Profit

You have initial capital W and can complete k projects. Project i needs capital[i] to start and gives profit[i]. Maximize final capital.

Decode — Min-Heap of Costs to Unlock, Max-Heap of Profits to Pick

Sort projects by capital. Use a min-heap of (capital, profit). Use a max-heap of profit. Greedy: For each of k rounds, add all affordable projects (capital ≤ W) to max-heap. Pick the most profitable (max-heap top). Add its profit to W. If no project is affordable and max-heap is empty, stop.

C++ Solution — O(n log n) int findMaximizedCapital(int k,int W,vector<int>& profits,vector<int>& capital){ int n=profits.size(); vector<pair<int,int>> projects(n); for(int i=0;i<n;i++) projects[i]={capital[i],profits[i]}; sort(projects.begin(),projects.end()); // Sort by capital priority_queue<int> maxP; // Max-heap of profits int idx=0; for(int i=0;i<k;i++){ // Unlock all affordable projects while(idx<n && projects[idx].first<=W) maxP.push(projects[idx++].second); if(maxP.empty()) break; // No affordable project W+=maxP.top(); maxP.pop(); // Pick most profitable } return W; }
TimeO(n log n)
SpaceO(n)
Q105 · HARD Minimum Cost to Connect Sticks (Huffman)
HeapGreedy
LeetCode #1167
Must KnowHuffman Coding Pattern

Connect sticks with minimum total cost. Each connection costs the sum of two sticks' lengths. Find the minimum total cost to connect all sticks into one.

Input[2,4,3]
Output14
Why2+3=5(cost5) 5+4=9(cost9) total=14
Decode — Always Merge the Two Smallest First (Greedy)

This is exactly Huffman coding. If you merge large sticks early, that cost propagates into all future merges. Merging the two smallest first minimizes total accumulated cost. Min-heap: repeatedly pop two smallest, push their sum, add sum to total cost.

sticks=[2,4,3] Heap (min): [2,3,4] Round 1: pop 2,3 → merge=5, cost+=5. heap=[4,5] Round 2: pop 4,5 → merge=9, cost+=9. heap=[9] Total cost = 14
C++ Solution — O(n log n) int connectSticks(vector<int>& sticks){ priority_queue<int,vector<int>,greater<int>> pq(sticks.begin(),sticks.end()); int cost=0; while(pq.size()>1){ int a=pq.top();pq.pop(); int b=pq.top();pq.pop(); cost+=a+b; pq.push(a+b); } return cost; }
TimeO(n log n)
SpaceO(n)
PatternHuffman coding · Optimal Merge Pattern
↑ back to top