239 lines
9.4 KiB
YAML
239 lines
9.4 KiB
YAML
title: Invert Binary Tree
|
|
slug: invert-binary-tree
|
|
difficulty: easy
|
|
leetcode_id: 226
|
|
leetcode_url: https://leetcode.com/problems/invert-binary-tree/
|
|
categories:
|
|
- trees
|
|
- recursion
|
|
patterns:
|
|
- tree-traversal
|
|
- dfs
|
|
- bfs
|
|
|
|
function_signature: "def invert_tree(root: TreeNode | None) -> TreeNode | None:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { root: [4, 2, 7, 1, 3, 6, 9] }
|
|
expected: [4, 7, 2, 9, 6, 3, 1]
|
|
- input: { root: [2, 1, 3] }
|
|
expected: [2, 3, 1]
|
|
- input: { root: [] }
|
|
expected: []
|
|
hidden:
|
|
- input: { root: [1] }
|
|
expected: [1]
|
|
- input: { root: [1, 2] }
|
|
expected: [1, null, 2]
|
|
- input: { root: [1, null, 2] }
|
|
expected: [1, 2]
|
|
- input: { root: [1, 2, 3, 4, 5, 6, 7] }
|
|
expected: [1, 3, 2, 7, 6, 5, 4]
|
|
- input: { root: [5, 3, 8, 1, 4, 7, 9] }
|
|
expected: [5, 8, 3, 9, 7, 4, 1]
|
|
- input: { root: [1, 2, 3] }
|
|
expected: [1, 3, 2]
|
|
|
|
description: |
|
|
Given the `root` of a binary tree, invert the tree, and return *its root*.
|
|
|
|
Inverting a binary tree means swapping the left and right children of every node in the tree, creating a mirror image of the original structure.
|
|
|
|
constraints: |
|
|
- The number of nodes in the tree is in the range `[0, 100]`
|
|
- `-100 <= Node.val <= 100`
|
|
|
|
examples:
|
|
- input: "root = [4,2,7,1,3,6,9]"
|
|
output: "[4,7,2,9,6,3,1]"
|
|
explanation: "The tree is inverted by swapping left and right children at every level. Node 4's children swap (2↔7), then node 7's children swap (6↔9) and node 2's children swap (1↔3)."
|
|
- input: "root = [2,1,3]"
|
|
output: "[2,3,1]"
|
|
explanation: "The root's left child (1) and right child (3) are swapped."
|
|
- input: "root = []"
|
|
output: "[]"
|
|
explanation: "An empty tree remains empty after inversion."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine holding a mirror up to a binary tree. The reflection you see is the inverted tree — every left branch becomes a right branch, and vice versa.
|
|
|
|
The key insight is that **inverting a tree is a recursive operation**: to invert a tree rooted at any node, you simply swap its left and right children, then recursively invert each subtree. This naturally follows the structure of the tree itself.
|
|
|
|
Think of it like this: if someone asked you to mirror-flip a family tree diagram, you'd swap each parent's children, then do the same for each of those children's subtrees. The operation is the same at every level — a perfect fit for recursion.
|
|
|
|
The beauty of this problem is that the recursive solution directly mirrors the problem definition: invert the left subtree, invert the right subtree, then swap them.
|
|
|
|
approach: |
|
|
We solve this using a **Recursive (DFS) Approach**:
|
|
|
|
**Step 1: Handle the base case**
|
|
|
|
- If the node is `None`, return `None` immediately
|
|
- This handles empty trees and serves as the recursion termination condition
|
|
|
|
|
|
|
|
**Step 2: Swap the children**
|
|
|
|
- Store the left child in a temporary variable (or use simultaneous assignment)
|
|
- Assign the right child to the left
|
|
- Assign the stored left child to the right
|
|
- This mirrors the current node's immediate children
|
|
|
|
|
|
|
|
**Step 3: Recursively invert subtrees**
|
|
|
|
- Call `invert_tree` on the new left child (which was originally the right child)
|
|
- Call `invert_tree` on the new right child (which was originally the left child)
|
|
- This ensures all descendants are also inverted
|
|
|
|
|
|
|
|
**Step 4: Return the root**
|
|
|
|
- Return the current node after its subtree has been fully inverted
|
|
- This allows the recursion to build back up correctly
|
|
|
|
|
|
|
|
The order of swapping vs recursing doesn't matter — you can swap first then recurse, or recurse first then swap. Both produce the same result because every node gets visited and swapped exactly once.
|
|
|
|
common_pitfalls:
|
|
- title: Forgetting the Base Case
|
|
description: |
|
|
Without checking for `None`, your recursion will crash when it tries to access `.left` or `.right` on a null node.
|
|
|
|
Always start recursive tree functions with:
|
|
```python
|
|
if not root:
|
|
return None
|
|
```
|
|
wrong_approach: "Directly accessing root.left without null check"
|
|
correct_approach: "Check if root is None before any operations"
|
|
|
|
- title: Only Swapping at One Level
|
|
description: |
|
|
A common mistake is to swap the root's children but forget to recursively process the subtrees.
|
|
|
|
For example, with `[4,2,7,1,3,6,9]`, only swapping at the root gives `[4,7,2,1,3,6,9]` — the grandchildren are in the wrong positions. You need to continue swapping at every level.
|
|
wrong_approach: "Only swapping root.left and root.right"
|
|
correct_approach: "Recursively invert both subtrees after swapping"
|
|
|
|
- title: Overwriting Before Saving
|
|
description: |
|
|
If you write `root.left = root.right` first, you lose the original left child before you can assign it to the right.
|
|
|
|
Use either a temporary variable or Python's simultaneous assignment:
|
|
```python
|
|
root.left, root.right = root.right, root.left
|
|
```
|
|
wrong_approach: "Sequential assignment without temp variable"
|
|
correct_approach: "Simultaneous swap or use temporary variable"
|
|
|
|
key_takeaways:
|
|
- "**Recursive tree operations**: Many tree problems have elegant recursive solutions where you process subtrees and combine results"
|
|
- "**Base case discipline**: Always handle the `None` case first in tree recursion"
|
|
- "**Multiple valid approaches**: This can be solved with DFS (recursive or stack-based) or BFS (queue-based) — all with the same complexity"
|
|
- "**Famous interview problem**: This problem gained notoriety when a senior engineer reportedly couldn't solve it on a whiteboard, highlighting that even simple problems require practice"
|
|
|
|
time_complexity: "O(n). We visit each node exactly once to swap its children, where n is the number of nodes in the tree."
|
|
space_complexity: "O(h). The recursion stack can grow to the height of the tree. In the worst case (skewed tree), this is O(n). For a balanced tree, it's O(log n)."
|
|
|
|
solutions:
|
|
- approach_name: Recursive DFS
|
|
is_optimal: true
|
|
code: |
|
|
class TreeNode:
|
|
def __init__(self, val=0, left=None, right=None):
|
|
self.val = val
|
|
self.left = left
|
|
self.right = right
|
|
|
|
def invert_tree(root: TreeNode | None) -> TreeNode | None:
|
|
# Base case: empty tree or leaf's null child
|
|
if not root:
|
|
return None
|
|
|
|
# Swap the left and right children
|
|
root.left, root.right = root.right, root.left
|
|
|
|
# Recursively invert both subtrees
|
|
invert_tree(root.left)
|
|
invert_tree(root.right)
|
|
|
|
# Return the root of the inverted tree
|
|
return root
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Each node is visited exactly once.
|
|
|
|
**Space Complexity:** O(h) — Recursion stack depth equals tree height.
|
|
|
|
This elegant solution directly mirrors the problem definition. We swap children at the current node, then recursively handle subtrees. The simultaneous assignment `root.left, root.right = root.right, root.left` safely swaps without needing a temporary variable.
|
|
|
|
- approach_name: Iterative BFS
|
|
is_optimal: true
|
|
code: |
|
|
from collections import deque
|
|
|
|
def invert_tree(root: TreeNode | None) -> TreeNode | None:
|
|
if not root:
|
|
return None
|
|
|
|
# Use a queue for level-order traversal
|
|
queue = deque([root])
|
|
|
|
while queue:
|
|
# Process the next node
|
|
node = queue.popleft()
|
|
|
|
# Swap its children
|
|
node.left, node.right = node.right, node.left
|
|
|
|
# Add children to queue for processing
|
|
if node.left:
|
|
queue.append(node.left)
|
|
if node.right:
|
|
queue.append(node.right)
|
|
|
|
return root
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Each node is visited exactly once.
|
|
|
|
**Space Complexity:** O(w) — Queue holds at most one level, where w is the maximum width of the tree. In the worst case (complete tree), this is O(n/2) = O(n).
|
|
|
|
This iterative approach uses BFS to visit nodes level by level. At each node, we swap its children and add them to the queue. This avoids recursion stack overhead but uses explicit queue memory instead.
|
|
|
|
- approach_name: Iterative DFS (Stack)
|
|
is_optimal: true
|
|
code: |
|
|
def invert_tree(root: TreeNode | None) -> TreeNode | None:
|
|
if not root:
|
|
return None
|
|
|
|
# Use a stack for depth-first traversal
|
|
stack = [root]
|
|
|
|
while stack:
|
|
# Process the next node
|
|
node = stack.pop()
|
|
|
|
# Swap its children
|
|
node.left, node.right = node.right, node.left
|
|
|
|
# Add children to stack for processing
|
|
if node.left:
|
|
stack.append(node.left)
|
|
if node.right:
|
|
stack.append(node.right)
|
|
|
|
return root
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Each node is visited exactly once.
|
|
|
|
**Space Complexity:** O(h) — Stack depth equals tree height in the worst case.
|
|
|
|
This converts the recursive DFS to an iterative approach using an explicit stack. The logic is identical to the recursive version but avoids potential stack overflow for very deep trees. The order of processing differs from BFS but the final result is the same.
|