BST — Worked LeetCode Examples
Scope — The BST problems that add one step to a template in
bst.md, each worked once as a problem card and filed under the card it reduces to; problems a template or an advanced card already works are listed here as pointers, not re-solved. See also: bst.md — the BST templates these problems reduce to; bst_advanced.md — the lazy iterator, recovering a broken BST, rank queries and the construction variants; tree_lca_distance.md — the root-to-leaf and any-to-any path problems (LC 112, 113, 124, 129, 257, 437), which need no ordering; binary_tree.md — LC 298 and the other unordered-tree DFS shapes.
LeetCode Problem Lists
Overview
The BST problem set, grouped by the bst.md template each one reduces to, in that sheet’s template order. A problem the template itself already solves is a row in the Index pointing at its home, not a second copy. The path-sum family that used to be filed here works on any binary tree, so it lives in tree_lca_distance.md.
Index
| LC | Problem | Reduces to | Difficulty |
|---|---|---|---|
| 776 | Split BST | bst § Template 3b — keep both sides instead of discarding one | Medium |
| 938 | Range Sum of BST | bst § Template 3b — sum the in-range values instead of reattaching them | Easy |
| 98 | Validate Binary Search Tree — worked in bst § Template 4, with the iterative form | bst § Template 4 | Medium |
| 449 | Serialize and Deserialize BST | bst § Template 4 — the bounds decide where each preorder value goes | Medium |
| 538 | Convert BST to Greater Tree | bst § Template 5, reversed | Medium |
| 1038 | Binary Search Tree to Greater Sum Tree — the same problem as LC 538, the same code | bst § Template 5, reversed | Medium |
| 173 | Binary Search Tree Iterator — worked in bst_advanced § Template 1 | bst § Template 5, paused between calls | Medium |
Trim to a Range Examples
Split BST — LC 776
Key point — trimming discards the side of the root that is out of range; splitting keeps it as the second tree. At each node, one of the two returned trees is the node itself, and the other comes from splitting only the child that can straddle
target, so the walk is one path.
Reduces to: bst § Template 3b — the same “the whole other side is on one side of the cut” step, but nothing is thrown away.
# python
# LC 776 - Split BST
# IDEA: root <= target -> root heads the small tree; split its right child and keep the small part;
# root > target -> root heads the big tree; split its left child and keep the big part
# time = O(h), space = O(h)
def splitBST(root, target):
if not root:
return [None, None] # [<= target, > target]
if root.val <= target:
small, big = splitBST(root.right, target)
root.right = small # the part of the right subtree that stays with root
return [root, big]
small, big = splitBST(root.left, target)
root.left = big # the part of the left subtree that stays with root
return [small, root]
// java
// LC 776 - Split BST
// IDEA: split only the child that straddles target, reattach the half that belongs with root
// time = O(h), space = O(h)
public TreeNode[] splitBST(TreeNode root, int target) {
if (root == null) {
return new TreeNode[]{null, null};
}
if (root.val <= target) {
TreeNode[] split = splitBST(root.right, target);
root.right = split[0];
return new TreeNode[]{root, split[1]};
}
TreeNode[] split = splitBST(root.left, target);
root.left = split[1];
return new TreeNode[]{split[0], root};
}
[4,2,6,1,3,5,7], target = 2
at 4: 4 > 2 -> split 4.left (2); 4 keeps the big half of it
at 2: 2 <= 2 -> split 2.right (3); 2 keeps the small half of it
at 3: 3 > 2 -> split 3.left (null) -> [null, null]; return [null, 3]
back at 2: 2.right = null -> return [2, 3]
back at 4: 4.left = 3 -> return [2, 4]
small tree [2,1] big tree [4,3,6,null,null,5,7]
Traps
- Reattaching the wrong half: when
root.val <= target,root.righttakessplit[0](the small half), notsplit[1]. Swapping them puts 3 under 2 in the example above. - Splitting both children is O(n) and still correct; only one child can straddle the cut.
| Operation | LC | Nodes removed | Returns |
|---|---|---|---|
| Delete | 450 | one | one root |
| Trim | 669 | every node outside [low, high] |
one root |
| Split | 776 | none | two roots |
Range Sum of BST — LC 938
Key point — the same three-way pruning as
trimBST: an out-of-range root rules out a whole side, so only the combine step changes, from reattaching children to adding values.
Reduces to: bst § Template 3b — sum instead of restructure.
# python
# IDEA: Template 3b's pruning; add the in-range values instead of reattaching them
# time = O(n), space = O(h)
# LC 938 - Range Sum of BST
def rangeSumBST(root, low, high):
if not root:
return 0
if root.val < low: # nothing on the left can be in range
return rangeSumBST(root.right, low, high)
if root.val > high: # nothing on the right can be in range
return rangeSumBST(root.left, low, high)
return (root.val
+ rangeSumBST(root.left, low, high)
+ rangeSumBST(root.right, low, high))
Validate with Bounds Examples
Serialize and Deserialize BST — LC 449
Key point — a BST’s preorder alone determines the tree: the next value belongs in the current subtree exactly when it lies inside that subtree’s
(low, high)interval. So the encoding needs no null markers, which is what makes it more compact than LC 297’s.
Reduces to: bst § Template 4 — the same interval carried down, used to place a value instead of to reject one.
# python
# LC 449 - Serialize and Deserialize BST
# IDEA: serialize = preorder values only; deserialize = rebuild with (low, high) bounds:
# the next value starts this subtree iff it fits the interval, else this subtree is empty
# time = O(n), space = O(n)
from collections import deque
class Codec:
def serialize(self, root):
vals = []
def preorder(node):
if not node:
return
vals.append(str(node.val))
preorder(node.left)
preorder(node.right)
preorder(root)
return ",".join(vals)
def deserialize(self, data):
vals = deque(int(x) for x in data.split(",")) if data else deque()
def build(low, high):
if not vals or not (low < vals[0] < high):
return None # the next value belongs to an ancestor
node = TreeNode(vals.popleft())
node.left = build(low, node.val)
node.right = build(node.val, high)
return node
return build(float("-inf"), float("inf"))
Traps
"".split(",")is[""], andint("")raises: the empty tree needs its own guard.- Strict
<assumes distinct values, which a BST guarantees. With duplicates, preorder alone no longer fixes the shape. - LC 297’s level-order codec with
#markers also passes here, but it ignores the BST and writes roughly twice as many tokens. Say why the bounds make the markers unnecessary.
Similar — LC 1008 (the deserialize half on its own, bst_advanced § Template 4) · LC 297 (any binary tree: markers are needed)
In-order Walk Examples
Convert BST to Greater Tree — LC 538
Key point — right → node → left visits values in descending order, so a running sum turns each node into the sum of every value greater than or equal to it. LC 1038 is the same problem.
Reduces to: bst § Template 5, reversed (Reverse Inorder for Descending).
# python
# IDEA: reverse in-order (right, node, left) with a running sum of the values seen so far
# time = O(n), space = O(h)
# LC 538 - Convert BST to Greater Tree
class Solution(object):
def convertBST(self, root):
self.running = 0
def reverse_inorder(node):
if not node:
return
reverse_inorder(node.right) # larger values first
self.running += node.val
node.val = self.running
reverse_inorder(node.left)
reverse_inorder(root)
return root