Recursion¶
Solve problems by breaking them into smaller subproblems.
Anatomy of Recursion¶
Every recursive function needs:
- Base case - Stops recursion
- Recursive case - Calls itself with smaller input
def factorial(n: int) -> int:
# Base case
if n <= 1:
return 1
# Recursive case
return n * factorial(n - 1)
Classic Examples¶
Fibonacci¶
def fibonacci(n: int) -> int:
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# With memoization
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_memo(n: int) -> int:
if n <= 1:
return n
return fibonacci_memo(n - 1) + fibonacci_memo(n - 2)
Sum of Array¶
Reverse String¶
Power¶
def power(base: int, exp: int) -> int:
if exp == 0:
return 1
return base * power(base, exp - 1)
# Optimized (O(log n))
def power_fast(base: int, exp: int) -> int:
if exp == 0:
return 1
if exp % 2 == 0:
return power_fast(base * base, exp // 2)
return base * power_fast(base, exp - 1)
Tree Recursion¶
Tree Traversal¶
def inorder(root: TreeNode | None) -> list[int]:
if not root:
return []
return inorder(root.left) + [root.val] + inorder(root.right)
def max_depth(root: TreeNode | None) -> int:
if not root:
return 0
return 1 + max(max_depth(root.left), max_depth(root.right))
Backtracking¶
Generate Permutations¶
def permute(nums: list[int]) -> list[list[int]]:
result: list[list[int]] = []
def backtrack(path: list[int], remaining: list[int]) -> None:
if not remaining:
result.append(path)
return
for i, num in enumerate(remaining):
backtrack(path + [num], remaining[:i] + remaining[i + 1:])
backtrack([], nums)
return result
N-Queens¶
def solve_n_queens(n: int) -> list[list[str]]:
result: list[list[str]] = []
def is_valid(board: list[str], row: int, col: int) -> bool:
for i in range(row):
if board[i][col] == 'Q':
return False
if col - (row - i) >= 0 and board[i][col - (row - i)] == 'Q':
return False
if col + (row - i) < n and board[i][col + (row - i)] == 'Q':
return False
return True
def solve(board: list[str], row: int) -> None:
if row == n:
result.append(board[:])
return
for col in range(n):
if is_valid(board, row, col):
board[row] = '.' * col + 'Q' + '.' * (n - col - 1)
solve(board, row + 1)
board[row] = '.' * n
solve(['.' * n] * n, 0)
return result
Recursion vs Iteration¶
| Aspect | Recursion | Iteration |
|---|---|---|
| Readability | Often cleaner | Can be verbose |
| Memory | Stack overhead | Usually less |
| Performance | Function call cost | Generally faster |
| Use case | Trees, divide & conquer | Simple loops |
Tail Recursion¶
# Not tail recursive
def factorial(n: int) -> int:
if n <= 1:
return 1
return n * factorial(n - 1) # Operation after call
# Tail recursive (Python doesn't optimize)
def factorial_tail(n: int, acc: int = 1) -> int:
if n <= 1:
return acc
return factorial_tail(n - 1, n * acc)
Practice Files¶
build/algorithms/03-recursion/fibonacci.pybuild/algorithms/03-recursion/permutations.pybuild/algorithms/03-recursion/backtracking.py
Next Topic¶
Continue to Dynamic Programming.