Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Analyzing Recursive Algorithms with Recurrence Relations

Recommended resources

Hello! Welcome to the next lesson in our journey to sharpen your algorithm skills.

In our previous lesson, we demystified space complexity, analyzing how memory usage scales with input size for both iterative and recursive algorithms. You learned to distinguish auxiliary space from total space and saw how the call stack is the primary driver of space usage in recursion.

Now, we'll complete our foundational understanding of complexity analysis by tackling the time complexity of recursive algorithms. This is where your preference for "first principles" will be particularly rewarding, as we move from simply counting loop iterations to modeling recursive work with mathematics.

Today's learning outcome is to analyze recursive algorithm complexity using recurrence relations. We'll break down a recursive function's runtime into a mathematical formula and then learn several powerful techniques to solve it.

1. From Recursive Code to a Recurrence Relation

A recurrence relation is an equation that defines a function's value in terms of its value on smaller inputs. This is a natural fit for analyzing recursive algorithms. To write one, we need to identify two key components from the code:

  1. Recursive Calls: How many recursive calls are made, and how much smaller is the input for each call?
  2. Work at Each Step: How much work is done in the function outside of the recursive calls? (e.g., comparisons, arithmetic, creating data structures).

Let's see how this works in practice.

Lecture 18: Recurrence relations

The following material from Cornell University's CS 3110 course provides excellent, code-driven examples of how to derive recurrence relations. It starts with a simple linear recursion and then moves to a more interesting divide-and-conquer example.

Read the initial sections that analyze the times1 and times2 functions. You can find these by looking for the respective OCaml code snippets. Focus on: How the code for times1 directly translates to the recurrence T(n) = T(n-1) + c. For times2, how the if/else logic for even and odd numbers leads to a more complex, conditional recurrence. The clever technique used to simplify the times2 analysis by 'charging' the cost of an odd-input call to the subsequent even-input call.

As you saw, a recursive algorithm's structure dictates the form of its recurrence relation.

  • Linear Recursion: An algorithm like times1 that calls itself once with a slightly smaller input (e.g., n-1) yields a recurrence like T(n) = T(n-1) + O(1).
  • Divide and Conquer: An algorithm like times2 or Merge Sort, which divides the problem into subproblems (e.g., n/2), yields recurrences like T(n) = aT(n/b) + f(n), where a is the number of subproblems, b is the factor by which the input size is reduced, and f(n) is the work done at each step.

Now that we can formulate these relations, how do we solve them to get a Big-O bound?


2. The Recursion Tree Method: Building Intuition

The recursion tree method is a visual and intuitive way to analyze the total work done by a recursive algorithm. It's particularly useful for building a hypothesis about the time complexity, which you can then prove formally.

The process involves four steps:

  1. Draw a tree representing the recursive calls. The root is the initial call, and its children are the subproblems it calls, and so on.
  2. Label each node with the cost (work done) at that specific step, excluding the cost of its children's recursive calls.
  3. Sum the costs for each level of the tree.
  4. Sum the costs of all levels to get the total runtime.

This video provides a clear, step-by-step demonstration.

Solved Recurrence Tree Method

The video 'Solved Recurrence Tree Method' by John Bowers provides an excellent walkthrough for building and analyzing a recursion tree.

Watch the entire video (about 6.5 minutes). Pay close attention to how the analysis is systematically structured: Identifying the work per node at each level. Calculating the total work per level (the 'row sum'). Determining the total height of the tree (when the base case is reached). Summing the work across all levels to get the final complexity.

The example in the video, T(n) = 2T(n/2) + 4n, is characteristic of algorithms like Merge Sort. The work at each level was constant (4n). Let's look at another example where the work per level forms a geometric series.

Recursion Tree Method: Example 3
This diagram illustrates the recursion tree for the recurrence `T(n) = 3T(n/4) + n^2`. The cost decreases at each level, forming a geometric progression. The total work is dominated by the cost at the root, `n^2`.

For this tree:

  • Level 0: Cost is cn^2.
  • Level 1: There are 3 nodes, each with cost c(n/4)^2. Total cost: 3 * c(n^2/16) = (3/16)cn^2.
  • Level 2: There are 3^2=9 nodes, each with cost c(n/4^2)^2. Total cost: 9 * c(n^2/256) = (9/256)cn^2 = (3/16)^2 cn^2.
  • Total Cost: The sum is a geometric series cn^2 * (1 + 3/16 + (3/16)^2 + ...) which is dominated by the first term. The entire sum is bounded by a constant factor of the root's cost, so the complexity is O(n^2).

1 Solving recurrences

The Stanford CS161 lecture notes provide a more detailed mathematical breakdown of this same example, showing how to sum the geometric series to find the bound.

Please read the section '1.2 Recursion tree'. It walks through the same example shown in the image above, formalizing the calculation of the total cost. This will solidify your understanding of how to handle recurrences where the cost per level is not constant.

The recursion tree gives us a strong intuitive guess. But for a formal interview setting, especially at the Principal level, being able to prove your guess is a significant differentiator.


3. The Substitution Method: A Formal Proof

The substitution method is how we formally prove that our guessed complexity is correct. It uses mathematical induction, which aligns perfectly with your preference for understanding proofs of correctness.

The process:

  1. Guess the solution (e.g., using the recursion tree method, or just from experience). Let's say we guess O(g(n)).
  2. Prove the guess by induction. We need to show that T(n) ≤ c * g(n) for some constant c > 0 and for all n ≥ n_0.
    • Inductive Hypothesis: Assume that the bound T(k) ≤ c * g(k) holds for all k < n.
    • Inductive Step: Substitute the recurrence relation into the inequality and use the inductive hypothesis to prove that the bound holds for n.

This is a method that demands precision. A small mistake can invalidate the entire proof.

Substitution method | Solving Recurrences | Data Structure & Algorithm | Appliedroots

This video from GATE Applied Course provides a rigorous and clear explanation of the substitution method, including a crucial example of a common pitfall.

Please watch from 01:02 to 15:08. This is the core of the lesson. Part 1 (01:02 - 09:42): Focus on the correct application for T(n) = 2T(n/2) + n, proving the O(n log n) bound. Notice how the inductive hypothesis T(n/2) ≤ c(n/2)log(n/2) is substituted in. Part 2 (09:42 - 15:08): This is critical. Pay close attention to why the proof fails for an incorrect guess of O(n). The key is that you must prove T(n) ≤ cn, not T(n) ≤ cn + n. The RHS of the inequality must match your hypothesis exactly.

Test your understanding!

A student is trying to prove that T(n) = T(n-1) + n is O(n). The recurrence represents an algorithm that iterates from n down to 1. Here is their flawed proof using the substitution method:

  1. Guess: T(n) = O(n). We want to prove T(n) ≤ cn for some constant c.
  2. Inductive Hypothesis: Assume T(n-1) ≤ c(n-1).
  3. Inductive Step:
    T(n) = T(n-1) + n
    T(n) ≤ c(n-1) + n (by substituting the hypothesis)
    T(n) ≤ cn - c + n
    T(n) ≤ (c+1)n - c

The student concludes: "Since (c+1)n - c is O(n), the proof is complete."

Why is this proof invalid?

Show answer

The proof is invalid for the same reason highlighted in the video. The goal was to prove that T(n) ≤ cn. However, the final step only managed to show that T(n) ≤ (c+1)n - c.

You cannot conclude the proof by saying the result is O(n). The inductive step requires you to prove the exact form of the hypothesis. The extra +n term accumulates at each step of the recursion. In reality, the solution to T(n) = T(n-1) + n is n + (n-1) + ... + 1 = n(n+1)/2, which is O(n^2), not O(n). The failure of the inductive proof was a strong hint that the original guess was wrong.


4. Advanced Technique: Change of Variables

Occasionally, you may encounter a recurrence that doesn't fit a standard pattern. A powerful technique for a Principal-level toolkit is the change of variables, which can transform a strange recurrence into a familiar one.

Consider T(n) = 2T(√n) + log n. The √n term is unusual.
Let's introduce a new variable, m = log₂n. This means n = 2^m, and √n = (2^m)^(1/2) = 2^(m/2).
If we substitute T(n) = T(2^m) with a new function S(m), we get:
S(m) = T(2^m) = 2T(2^(m/2)) + m = 2S(m/2) + m.

This is the exact recurrence for Merge Sort! We know its solution is S(m) = O(m log m). Now we just substitute back:
T(n) = S(m) = O(m log m) = O(log n * log(log n)).

1 Solving recurrences

The Stanford lecture notes briefly cover this powerful technique.

Quickly read section '3.1 Change of variables'. It formalizes the example we just discussed. Seeing it in a formal context will help it stick.


Conclusion

You now have a robust framework for analyzing the runtime of recursive algorithms from first principles.

Key Takeaways:

  • Recurrence Relations: They are the mathematical model of a recursive algorithm's runtime, defined by the number of subproblems, the reduction in input size, and the work done at each step.
  • Recursion Tree Method: A fantastic visual tool for building an intuition and forming a hypothesis about an algorithm's complexity. Summing the work at each level is the key.
  • Substitution Method: The formal, proof-based technique to verify your hypothesis. It requires mathematical rigor—you must prove your guess holds in its exact form, not just a looser Big-O bound.
  • Technique Synergy: These methods work together. Use the recursion tree to guess, and the substitution method to prove.

Next Lesson Preview:
You may have noticed that many divide-and-conquer algorithms result in recurrences of the form T(n) = aT(n/b) + f(n). While you can solve these from scratch every time, there is a powerful shortcut. In our next lesson, we will explore the Master Theorem, a "cookbook" recipe for solving this common class of recurrences instantly.