DSA sheet · Trees · Construction & serialization pattern
Invert Binary Tree
This is the first problem of a new pattern in the sheet: construction / serialization, where we build, rebuild or reshape a tree. Inverting is the gentlest start. We don't make new nodes, we just swap the left and right child at every node, and the tree becomes its own mirror image. The teacher also uses this video to show how to think your way to code: write the part you understand, then let the dry run tell you what's missing. She solves it with DFS (her favourite here), then BFS.
Every problem 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 · What you must know before starting
- Part A · Invert Binary Tree with DFS
- Part B · Invert Binary Tree with BFS
- Part C · Revision page
Part 0 · Before starting
- Mirror image: what you'd see if you held the tree up to a mirror. Everything on the left appears on the right, and everything on the right appears on the left. This holds at every level, not just at the top.
- Height (h): the number of levels. Balanced → about log n. Skewed (a straight line) → n.
- Width (w): the largest number of nodes on any single level. In a full tree, the bottom level is the widest, with about n/2 nodes.
- Swapping with a temp variable: to swap a and b, you need a third box. Otherwise the first assignment wipes out one value.
Python can also dothe classic swap
temp = a # keep a safe a = b # now a and b are both the old b b = temp # put the old a into b
a, b = b, ain one line. It does the same thing safely.
Part A · Invert Binary Tree with DFS
LeetCode 226
1The question in simple words
Given the root of a binary tree, turn it into its mirror image and return the root.
4
/ \
2 7
/ \ / \
1 3 6 9 4
/ \
7 2
/ \ / \
9 6 3 1The root 4 stays where it is. 2 was the left child and is now the right child. 7 was the right child and is now the left child. And below them, 1 and 3 also swapped, and so did 6 and 9.
→ No. If you only swap at the root, you get 4 → (7, 2), but 2 still has 1 on its left and 3 on its right. In a mirror, 1 must be on the right now. So the same swap has to happen at every node: the root, then 2, then 7, then their children, and so on down to the leaves.
2What the constraints tell us
- Nodes: 0 to 100 → the tree can be empty, so we need a base case for None.
- The teacher repeats her rule: in DFS you always need a base case anyway, because the recursion will reach None children sooner or later.
- In BFS you need it only if the tree can be empty. With at least one node, the root goes into the queue and the loop ends by itself. Here 0 is allowed, so BFS needs it too.
- Values: −100 to 100. We don't even do maths on them, we only move nodes around. An int can hold up to about 10⁹, so nothing to worry about.
- n ≤ 100 is tiny. Any reasonable solution is fast.
3Intuition: a swap at every node
Stand on a node. You can see its left child and its right child. Swap them, meaning the whole branches, not just the numbers. Then walk into each child and do the same thing there. When every node has swapped its two children, the whole tree is mirrored.
→ The nodes (the links). We change which node
root.left points to and which node root.right points to. When we move the node 2 to the right, everything hanging under 2 (1 and 3) moves with it in one go. Swapping just the numbers 2 and 7 would leave their children behind on the wrong sides.4Building the code step by step (the teacher's way)
Her tip: don't try to write the whole code in one go. Write the parts you understand. Later, when you dry-run it, you'll see if a line needs to move, or if a variable or line is missing. That's exactly what happens here.
Step 1: the base case, and what should it return?
We know DFS needs "if the node is None, return…" but return what? Think about what the parent wants from its children. Does the parent need some number back, like a height or a sum? No. The parent just wants its two children swapped. So at first she writes a plain return.
Later, when she runs the Java code, it fails to compile. The function's return type is TreeNode, so it must return something. She changes it to return null. It also covers the case where the whole tree is empty: the answer for an empty tree is an empty tree.
return OK?→ It works, because a plain
return gives back None in Python. But write return None to make it clear that "empty tree in, empty tree out". That's the same fix the teacher made.Step 2: swap the two children of the current node
We're standing on the root, so we can reach root.left and root.right. Swap them with a temp variable, like swapping two numbers:
temp = root.left: keep the left branch (2 with 1, 3) safe.root.left = root.right: now the left link points at 7.root.right = temp: the right link gets the saved 2-branch.
The teacher draws the tree between lines 2 and 3. It's a great way to see why the temp is needed:
4
/ \
7 7 ← same 7-branch twice!
/ \ / \
6 9 6 9
temp → 2 (1, 3) ← still safe 4
/ \
7 2
/ \ / \
6 9 1 3Half-way through, both links point at the 7-branch, and the 2-branch survives only because temp holds it. Without the temp, the 2-branch would be lost forever.
Step 3: the root is right now, but the lower levels aren't
Look at the "after line 3" picture. 4, 7 and 2 are in the right places, but under 7 we have 6, 9, and the mirror needs 9, 6. Under 2 we have 1, 3 instead of 3, 1. So we repeat the same job for each child: call the same function on root.left and on root.right.
- Call on root.left (that's now the 7): it swaps 6 and 9 → 7 → (9, 6).
- Call on root.right (that's now the 2): it swaps 1 and 3 → 2 → (3, 1).
Step 4: what to return at the end?
The question wants the root of the inverted tree back, so the last line is return root.
→ No. Those calls change the tree in place, by changing links on nodes that are already attached. So we don't need their return values. The
return root matters only for the very first call, the one LeetCode makes. LeetCode takes that root and checks the tree from it.→ Yes. Swapping before the calls (the teacher's order, like preorder) and swapping after both calls (like postorder) both work, because each node's swap happens exactly once either way. What you must not do is call left, then swap, then call "right". After the swap, "right" is the branch you already inverted, so you would undo it.
5Approach steps
- If the node is None → return None.
- Swap its left and right children (temp variable).
- Invert the left child's subtree, then the right child's subtree (recursively).
- Return the node.
6Code (Python)
class Solution:
def invertTree(self, root):
if root is None: # empty spot: nothing to swap
return None
temp = root.left # keep the left branch safe
root.left = root.right # right branch moves to the left
root.right = temp # old left branch moves to the right
self.invertTree(root.left) # now mirror everything below, left...
self.invertTree(root.right) # ...and right
return root # the caller wants the root backclass Solution:
def invertTree(self, root):
if root is None:
return None
# invert both children, then attach them on the opposite sides
root.left, root.right = self.invertTree(root.right), self.invertTree(root.left)
return rootThe short version uses the return values: "the inverted right branch becomes my left, the inverted left branch becomes my right". Python evaluates both calls before assigning, so no temp is needed.
7Code line by line
| line | what it means |
|---|---|
| if root is None: return None | Base case. Stops the recursion below leaves. Also answers the empty-tree case (0 nodes allowed). |
| temp = root.left | Save the whole left branch before we overwrite its link. |
| root.left = root.right | The left link now points to the right branch. (For a moment both links point to it.) |
| root.right = temp | The right link gets the saved old left branch. This node's swap is done. |
| self.invertTree(root.left) | Mirror everything inside the new left branch. |
| self.invertTree(root.right) | Mirror everything inside the new right branch. |
| return root | Give the (same) root back to whoever called us. It matters for the first call. |
8Dry run using the call stack
Tree 4 → (2 → 1, 3), (7 → 6, 9). "N" = None.
- inv(4): swap → 4's left is 7, right is 2. Call inv(4.left) = inv(7). inv(4) waits.
- inv(7): swap 6 and 9 → 7 → (9, 6). Call inv(9).
- inv(9): a leaf. Swap None with None (no change). inv(N) → None, inv(N) → None. Return 9.
- Back in inv(7): call inv(6), also a leaf → nothing changes → return 6. inv(7) returns 7 and leaves the stack.
- Back in inv(4): call inv(4.right) = inv(2). Swap 1 and 3 → 2 → (3, 1).
- inv(3) and inv(1) are leaves → nothing changes. inv(2) returns 2.
- inv(4) returns 4. The tree is now 4 → (7 → 9, 6), (2 → 3, 1). ✓ That's the mirror.
4
/ \
7 2
/ \ / \
6 9 1 3 4
/ \
7 2
/ \ / \
9 6 1 3 4
/ \
7 2
/ \ / \
9 6 3 19Complexity & remember
- Time O(n): every node is visited once and does one swap. With n ≤ 100, that's about 100 steps, very fast.
- Space O(h): the call stack holds one call per level of the current path. Balanced → O(log n) (the teacher's figure). Skewed → O(n).
left and right (temp!) · recurse on both · return root. Swap links, not values. Do it at every node.Part B · Invert Binary Tree with BFS
Same question and the same swap. Only the order we visit nodes changes: level by level with a queue, the way we did level order traversal before.
1Intuition
Put 4 in the queue. When we pop 4, we don't need its value for anything. We just swap its two children, exactly as in DFS. Then we push those children (now 7, 2) so that later we pop them and swap their children. Every node is popped once and swapped once.
- In DFS, the recursive calls on
root.leftandroot.righttake us to the next nodes. - In BFS, pushing the children into the queue does that job.
2Constraints for BFS
Zero nodes is allowed. If the root is None, we must return None before we start, or we'd pop None and crash on None.left.
3Approach steps
- If root is None → return None.
- Queue starts with the root.
- While the queue isn't empty: pop a node and swap its left and right children.
- Push the children that exist (left, then right).
- When the queue is empty → return root.
4Code (Python)
from collections import deque
class Solution:
def invertTree(self, root):
if root is None: # 0 nodes is allowed
return None
queue = deque([root])
while queue:
node = queue.popleft()
temp = node.left # same swap as DFS
node.left = node.right
node.right = temp
if node.left: # line up the next level
queue.append(node.left)
if node.right:
queue.append(node.right)
return root5Code line by line
| line | what it means |
|---|---|
| if root is None: return None | BFS base case, needed only because the tree can be empty. |
| queue = deque([root]) | Start with level 0. |
| node = queue.popleft() | Take the next node in level order. |
| temp / left / right lines | The same three-line swap as DFS. This node is done. |
| if node.left: queue.append(...) | Push only real children. They are waiting to have their children swapped. |
| return root | Every node was swapped, so give back the root. |
6Dry run: watch the queue
Order of swaps: 4, 7, 2, then the leaves. That's level by level. DFS swapped in the order 4, 7, 9, 6, 2, 3, 1. Different order, same final tree.
7Complexity & DFS vs BFS
- Time O(n): every node is popped once, and no node is repeated.
- Space O(w), the maximum width, instead of the height. The queue holds about one level at a time. In the example: level 0 has 1 node, level 1 has 2, level 2 has 4. The widest level wins. For a full tree that bottom level has about n/2 nodes, so O(n).
Both are efficient. The teacher still prefers DFS for inverting trees: it's shorter, and the stack only grows with the height, not the width.
Part C · Revision page
| DFS (recursion) | BFS (queue) | |
|---|---|---|
| how we reach the next nodes | call on root.left, root.right | push children into the queue |
| work at each node | the same 3-line swap (temp → left = right → right = temp) | |
| base case | always: None → return None | only because n can be 0 |
| order of swaps (example) | 4, 7, 9, 6, 2, 3, 1 | 4, 7, 2, 9, 6, 3, 1 |
| time | O(n) | O(n) |
| space | O(h): height | O(w): width |
| teacher's pick | preferred | also fine |
2. Swap the links (whole branches), using a temp (or
a, b = b, a).3. DFS: None → None, swap, recurse both sides, return root.
4. BFS: pop, swap, push real children, return root.
5. DFS space = height, BFS space = width. Both O(n) time.
✗ swapping values instead of nodes (children stay on the wrong side)
✗
root.left = root.right; root.right = root.left without a temp (both sides end up the same)✗ "recurse left, swap, recurse right" (the right call re-inverts the branch you just did)
✗ forgetting
return root at the end, or forgetting the empty-tree check in BFSdef level_order(root):
out, q = [], [root]
while q:
n = q.pop(0)
if n:
out.append(n.val)
q += [n.left, n.right]
return out
root = TreeNode(4, TreeNode(2, TreeNode(1), TreeNode(3)), TreeNode(7, TreeNode(6), TreeNode(9)))
s = Solution()
print(level_order(s.invertTree(root))) # [4, 7, 2, 9, 6, 3, 1]
print(level_order(s.invertTree(TreeNode(2, TreeNode(1), TreeNode(3))))) # [2, 3, 1]
print(s.invertTree(None)) # NoneBased on this video: Invert Binary Tree | DFS & BFS