Recursion

A function solves a problem by calling itself on a smaller piece of the same problem. It is the base for tree and graph traversal, divide and conquer, backtracking and dynamic programming.

Overview

The idea

A recursive function solves a problem by handing a smaller copy of the same problem to itself, then using the answer. It needs two parts and one guarantee, and it breaks without any of them.

| Part | What it does | Factorial example | |---|---|---| | Base case | answers the smallest input directly, with no further call | n [!TIP] > Recursion depth equals the tree height. A balanced tree of a million nodes is about 20 levels deep, but a skewed one (a sorted insert into a plain binary search tree) is a million levels deep. Ask about the input shape before you promise recursion is safe.

The five shapes you will meet

  • Linear — 1 — use one smaller answer — Reverse String, Merge Two Sorted Lists, Pow(x, n)
  • Divide and conquer — 2 or more, on disjoint halves — merge the halves — Sort List, Different Ways to Add Parentheses
  • Tree recursion — one per child — combine child results — Maximum Depth, Lowest Common Ancestor
  • Backtracking — one per choice — record full solutions, undo the choice — Subsets, Permutations, Generate Parentheses
  • Memoized — several, overlapping — store and reuse — Fibonacci, Regular Expression Matching

Backtracking has its own topic, and so does dynamic programming. This topic is the base both build on.

Spotting a recursive problem

  • Input is a tree, or a nested structure (brackets, JSON, directories) — tree recursion on each child
  • The answer for n is built from the answer for n - 1 or n / 2 — linear recursion
  • Split in half, solve each half, join — divide and conquer
  • Generate all combinations, subsets or arrangements — backtracking
  • The same subproblem appears on many branches — recursion plus a memo

What you will be able to do

  • Write any recursive function by naming its base case, its shrink step and its combine step.
  • Trace a call stack on a small input and state the stack space it uses.
  • Estimate time from the recursion tree, and recognize the common recurrences.
  • Add memoization when subproblems repeat, and explain the drop in cost.
  • Rewrite a recursive solution as a loop when the depth could be too large.

When to use it

  • Walking a tree or graph, where each child is a smaller copy of the same problem
  • Divide and conquer: merge sort, quicksort, fast power
  • Generating all subsets, combinations or permutations (backtracking)
  • Parsing nested input such as brackets or encoded strings (Decode String)
  • Any problem where the answer for n is built from answers for smaller inputs
  • As the first step toward dynamic programming: write the recursion, then add a memo

Sign up free to read the full topic

More Coding & DSA topics

Start free on Cariara · Coding problems · System design studio