DSA sheet · Trees · Binary search tree pattern

Merge Two Binary Trees

The sheet calls this "merge two BSTs", but the video really covers two different questions with that name. On LeetCode, you lay one tree on top of another and add up the nodes that overlap. The trees there are normal binary trees, not BSTs. On GFG, you get two real BSTs and must return all their values as one sorted array. The teacher solves both. The first one teaches a key habit of the construction pattern: when a recursive call returns a node, you must store it somewhere. The second one reuses two things we already know: inorder of a BST is sorted, and how to merge two sorted arrays.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the conditions from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

given by LeetCode, don't write this in the solution
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

Binary tree vs binary search tree (BST)

Inorder traversal

Inorder = left subtree, then the node, then the right subtree. On a BST this visits the values in sorted order, because everything smaller sits on the left. We need this fact in Part B.

Overlap

Picture putting tree 2 on top of tree 1, root on root. Two nodes overlap when both trees have a node at the same position (same path of lefts and rights from the root).

Part A · Merge Two Binary Trees (LeetCode) with DFS

LeetCode 617

1The question in simple words

You get two binary trees, root1 and root2. Lay one over the other and build the merged tree:

Return the root of the merged tree.

root1
      1
     / \
    3   2
   /
  5
root2
      2
     / \
    1   3
     \   \
      4   7
merged
      3
     / \
    4   5
   / \   \
  5   4   7

Root: 1 + 2 = 3. Left: 3 + 1 = 4. Left of that: 5 in tree 1, nothing in tree 2 → stays 5. Right of 4: nothing in tree 1, 4 in tree 2 → 4. Right of the root: 2 + 3 = 5, and its right child is only in tree 2 → 7.

2What the constraints tell us

3Intuition: two fingers again, and write the answer into tree 1

Like in Same Tree, put one finger on each tree and move them together: left with left, right with right. At each position, the finger tells you whether a node exists there.

Where do we keep the answer? We could build a brand-new tree, but that means creating up to n new nodes. The teacher's choice: use tree 1 as the result. Wherever both nodes exist, overwrite tree 1's value with the sum. Wherever tree 1 has nothing but tree 2 has a node, just borrow tree 2's node. (Using tree 2 as the result works the same way, with the roles swapped.)

Doubt: the output is a whole tree anyway, so isn't the space O(n) either way?
→ The teacher agrees the returned tree itself is n nodes. But reusing tree 1 means we create no new nodes. Existing nodes just get updated values, and missing parts are borrowed from tree 2. That is clearly better than allocating a whole copy.

4Building the conditions from examples

She first asks when the addition can happen at all:

Base case 1: root1 is None → return root2

If tree 1 has nothing here, merging "nothing" with tree 2's part gives tree 2's part. Return root2.

Doubt 1: what if both are None? Don't I need a separate check?
→ No. If root1 is None we return root2. If root2 is also None, we return None, which is exactly right ("nothing + nothing = nothing"). One line covers both cases.

Base case 2: root2 is None → return root1

We only get here if root1 exists. Tree 2 has nothing to add, so tree 1's part stays as it is. Return root1.

Doubt 2: why check root2 before touching root2.val or going left and right?
→ We can only read root2.val, root2.left and root2.right if root2 exists. If it doesn't, there's nothing to walk into, so we stop early with tree 1's subtree. After these two checks, both nodes surely exist.

Both exist → add, then go left and right

root1.val += root2.val. Then the children. With root1's left child, which node of tree 2 overlaps? Its left child, of course. So the calls are (root1.left, root2.left) and (root1.right, root2.right).

The key habit: save what the call returns

The function returns a node (the root of the merged subtree). That node must be hung back in the right place. Whatever the left call returns must become root1.left, and the right call's result becomes root1.right.

Doubt 3: why is assigning needed? Isn't tree 1 already changed in place?
→ Values are changed in place, yes. But look at the spot where tree 1 is None and tree 2 has 4. The call returns tree 2's node 4. If we don't write root1.left = ... or root1.right = ..., that node is never linked into tree 1 and it is lost. This is why the teacher calls it construction: a returned root must always be stored.

Finally return root1

After both sides are done, this node is complete. Return root1 so that the parent can store it in its own left or right.

Her slip in the editorWhen she first ran the code, she had used the wrong variable name and it failed. She corrected it with "it's root1, my bad": the merged tree is tree 1, so we read from and return root1. Check your names when you write it.

5Approach steps

  1. If root1 is None → return root2.
  2. If root2 is None → return root1.
  3. Both exist: root1.val += root2.val.
  4. root1.left = merge(root1.left, root2.left).
  5. root1.right = merge(root1.right, root2.right).
  6. Return root1.

6Code (Python)

Merge Two Binary Trees, reuse tree 1
class Solution:
    def mergeTrees(self, root1, root2):
        if root1 is None:            # nothing in tree 1 (also covers both None)
            return root2
        if root2 is None:            # nothing to add from tree 2
            return root1
        root1.val += root2.val       # both exist: add into tree 1
        root1.left = self.mergeTrees(root1.left, root2.left)      # save the result!
        root1.right = self.mergeTrees(root1.right, root2.right)
        return root1

7Code line by line

linewhat it means
if root1 is None: return root2Tree 1 is empty here, so use tree 2's part (maybe None). This is also the empty-tree case.
if root2 is None: return root1Tree 2 is empty here, so tree 1's part stays. No need to go deeper.
root1.val += root2.valOverlap: write the sum into tree 1's node.
root1.left = self.mergeTrees(...)Merge the two left subtrees and hang the result on root1's left.
root1.right = self.mergeTrees(...)Same for the right side.
return root1Hand the finished subtree back to the parent call.

8Dry run using the call stack

The trees from step 1. f(a, b) means mergeTrees(a, b). N = None.

  1. f(1, 2): both exist → 1 becomes 3. Go left. (f(1,2) waits.)
  2. f(3, 1): both exist → 3 becomes 4. Go left.
  3. f(5, N): root1 = 5 exists, root2 is None → return 5. It's stored as 4's left (no change).
  4. Back in f(3,1), go right: f(N, 4): root1 is None → return tree 2's node 4. Stored as root1.right, so 4 now has right child 4.
  5. f(3,1) is done → returns its node (value 4) to f(1,2), which stores it as its left.
  6. f(1,2) goes right: f(2, 3): both exist → 2 becomes 5. Go left.
  7. f(N, N): root1 is None → return root2, which is None. 5's left stays None.
  8. Go right: f(N, 7): root1 is None → return node 7. Stored as 5's right.
  9. f(2,3) returns its node (value 5) to f(1,2), stored as its right.
  10. f(1,2) returns its node (value 3). The stack is empty. Final tree: 3 (4 (5, 4), 5 (–, 7)) ✓
stack at step 3
f(1,2)f(3,1)f(5,N) → 5
stack at step 4
f(1,2)f(3,1)f(N,4) → 4
stack at step 8
f(1,2)f(2,3)f(N,7) → 7

9Complexity & remember

Remember Merge Treesroot1 None → root2 · root2 None → root1 · add into root1 · root1.left = f(L, L), root1.right = f(R, R) · return root1.

Part B · Merge Two BSTs into a sorted array (GFG)

GFG "Merge two BSTs"

1The question in simple words

The GFG problem with the same name is a different task. You get two BSTs. Return one sorted list with every value from both trees, duplicates included. You don't build any tree.

BST 1
    3
   / \
  1   5
BST 2
    4
   / \
  2   5

Answer: [1, 2, 3, 4, 5, 5]. Note that 5 appears twice, because it is in both trees.

2What the constraints tell us

3Intuition: two sorted arrays for free

4Building the merge logic

Doubt 1: what if a[i] == b[j]?
→ Take either one and move that pointer. The other copy is taken in the next step, so both duplicates end up in the answer. Here we use <=, which takes from array 1 first.
Doubt 2: why not just put all values in one list and sort it?
→ That also works, but sorting costs O(n log n). The merge uses the fact that both lists are already sorted, so it's O(n). The teacher also mentions an even better version that walks both trees at the same time using stacks (it saves the two arrays). She didn't cover it in this video and says the array version is enough for now.

5Approach steps

  1. Inorder of BST 1 → array a. Inorder of BST 2 → array b.
  2. i = j = 0, empty res.
  3. While both pointers are in range: append the smaller of a[i], b[j] and move that pointer.
  4. Copy whatever is left in a, then whatever is left in b.
  5. Return res.

6Code (Python)

Merge two BSTs into a sorted list (GFG)
class Solution:
    def merge(self, root1, root2):
        a, b = [], []
        self.inorder(root1, a)
        self.inorder(root2, b)

        res = []
        i = j = 0
        while i < len(a) and j < len(b):
            if a[i] <= b[j]:
                res.append(a[i])
                i += 1
            else:
                res.append(b[j])
                j += 1
        while i < len(a):              # leftovers from array 1
            res.append(a[i])
            i += 1
        while j < len(b):              # leftovers from array 2
            res.append(b[j])
            j += 1
        return res

    def inorder(self, node, arr):
        if node is None:
            return
        self.inorder(node.left, arr)   # left
        arr.append(node.val)           # node
        self.inorder(node.right, arr)  # right

7Code line by line

linewhat it means
self.inorder(root1, a)Fill a with BST 1's values. They come out sorted because of the BST rule.
while i < len(a) and j < len(b):Compare only while both arrays still have values.
if a[i] <= b[j]: ... i += 1Array 1's front is smaller (or equal), so take it and move i.
else: ... j += 1Array 2's front is smaller, so take it and move j.
while i < len(a): ...Array 2 ran out first. Copy the rest of array 1.
while j < len(b): ...Array 1 ran out first. Copy the rest of array 2. (Only one of these two loops actually does anything.)
self.inorder(node.left, arr) arr.append(node.val) self.inorder(node.right, arr)Left, node, right: the inorder order.

8Dry run

a = [1, 3, 5], b = [2, 4, 5].

stepa[i]b[j]takeres
1121 (i → 1)[1]
2322 (j → 1)[1, 2]
3343 (i → 2)[1, 2, 3]
4544 (j → 2)[1, 2, 3, 4]
5555 from a (i → 3, out of range)[1, 2, 3, 4, 5]
6–5leftover loop: 5 from b[1, 2, 3, 4, 5, 5]

Final answer [1, 2, 3, 4, 5, 5] ✓

9Complexity & remember

Remember the GFG versionBST + inorder = sorted array. Two sorted arrays → two-pointer merge, then copy the leftovers.

Part C · Revision page

LeetCode 617 (merge trees)GFG (merge two BSTs)
inputtwo normal binary treestwo BSTs
outputa merged tree (root)a sorted list of all values
core ideatwo fingers, add on overlap, borrow when one side is missinginorder → two sorted arrays → merge
timeO(n)O(n + m)
spaceO(height) stackO(n + m)
situation (LeetCode)what we returnwhy
root1 None (root2 anything)root2nothing to add. Also gives None when both are None
root2 Noneroot1tree 1's part stays unchanged
both existroot1 after adding and fixing its childrentree 1 is the result
for comparison: the version that builds a brand-new tree (extra nodes)
class Solution:
    def mergeTrees(self, root1, root2):
        if root1 is None and root2 is None:
            return None
        v1 = root1.val if root1 else 0
        v2 = root2.val if root2 else 0
        node = TreeNode(v1 + v2)                 # a new node every time
        node.left = self.mergeTrees(root1.left if root1 else None,
                                    root2.left if root2 else None)
        node.right = self.mergeTrees(root1.right if root1 else None,
                                     root2.right if root2 else None)
        return node

This one creates a node for every position and walks every node of both trees. The teacher's version reuses tree 1 and stops early when one side is missing.

If you remember only 5 lines 1. Move both fingers together: left with left, right with right.
2. if root1 is None: return root2 also covers "both None".
3. if root2 is None: return root1.
4. Add into root1, and store the returned children in root1.left and root1.right.
5. GFG version: inorder both BSTs, then a two-pointer merge.
Mistakes to avoid ✗ calling mergeTrees(root1.left, root2.left) without assigning the result (borrowed nodes get lost)
✗ reading root2.val before checking root2 is None
✗ returning the wrong variable at the end (her slip: it must be root1)
✗ in the GFG merge, forgetting the leftover loops
✗ dropping duplicates with a set
test it yourself (paste under the matching solution)
r1 = TreeNode(1, TreeNode(3, TreeNode(5)), TreeNode(2))
r2 = TreeNode(2, TreeNode(1, None, TreeNode(4)), TreeNode(3, None, TreeNode(7)))
m = Solution().mergeTrees(r1, r2)
print(m.val, m.left.val, m.right.val)        # 3 4 5

b1 = TreeNode(3, TreeNode(1), TreeNode(5))
b2 = TreeNode(4, TreeNode(2), TreeNode(5))
print(Solution().merge(b1, b2))              # [1, 2, 3, 4, 5, 5]

Based on this video: Merge Two Binary Trees / Merge Two BSTs