Fundamentals of
Recursion
Lecture Flow
1. Pre-requisites
2. Problem definitions
3. Basic Concepts
4. Time and Space Complexity of Data Structure
5. Common pitfalls
6. Practice questions and Resources
7. Quote of the day
Pre-requisites
● Understanding iterative program flow
● Functions, local variables and global variables
● Stack data structures
● Willingness to learn
Thinking recursively
Recursion is an important concept in computer science. It is a
foundation for many other algorithms and data structures. However, the
concept of recursion can be tricky to grasp for many beginners.
4
Real Life Example
Imagine you are in a long queue and the reception asks you how many
people there are in the line. How do you count the number of people in
the queue?
5
Brainstorm
● Let’s assume each person in the queue
○ Can increment numbers by 1
○ Can request other people
● What if I asked the person behind me “how many people there are” and
whatever their response is, I can do plus 1 and inform the reception? And
● What if the person I asked, asks the person behind them “how many people
there are” and whatever they get, they do plus 1 and inform me?
•
•
•
6
1 Asking and Aggregating steps
How many
people are 2
How many 4
there? 3
How many people behind
people you including 5
reception behind you yourself? Only Me
Let me
including
check…
yourself?
2 + 1= 3
7
6
There
are 2
Third
people
Second person
First person
person
Stopping
7 case
Can we simulate
this with code?
● Since we are doing
the same thing again
and again let’s use
one function to do
the task
8
Does this work?
def ask(person):
behind = ask(person + 1)
people = behind + 1
return people
9
What happens?
behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
return people
people = behind + 1
return people
return people
return people
We will never get to return because we are calling
the function non stop. What should we do?
10
Does this work?
def ask(person):
if noMorePeople:
return 1
behind = ask(person + 1)
people = behind + 1
return people
11
What happens?
if noMorePeoplen:
return 1 if noMorePeople:
return 1 if noMorePeople:
return 1 if noMorePeople:
behind = ask(person + 1)
return 1
people = behind + 1 behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
people = behind + 1 behind = ask(person + 1)
return people
people = behind + 1
return people
return people
return people
Then now we return the number of people to our
first caller
12
Real Life Example 2
Imagine some one give you a matryoshka and ask you to count the
number dolls, how would you solve the problem?
Brainstorm
14
Brainstorm
15
Brainstorm
16
Brainstorm
17
Brainstorm
18
Brainstorm
Base Case
return 1
19
Brainstorm
return 1+1
Base Case
return 1
20
Brainstorm
return 1+2
return 2
Base Case
return 1
21
Brainstorm
return 1+3
return 3
return 2
Base Case
return 1
22
Brainstorm
return 1+4
return 4
return 3
return 2
Base Case
return 1
23
Can we simulate
this with code?
● Since we are doing
the same thing again
and again let’s use
one function to do
the task
24
Does this work?
def count(matryoshka):
inside = count(matryoshka.child)
return inside + 1
25
What happens?
inside = count(matryoshka.child)
return inside + 1
inside = count(matryoshka.child)
return inside + 1
inside = count(matryoshka.child)
return inside + 1
• • •
We will never get to return because we are calling
the function non stop. What should we do?
26
How about this?
def count(matryoshka):
if matryoshka.empty:
return 1
inside = count(matryoshka.child)
return inside + 1
27
What happens?
if matryoshka.empty:
return 1
if matryoshka.empty:
inside = count(matryoshka.child) return 1
return inside + 1 if matryoshka.empty:
inside = count(matryoshka.child) return 1
return inside + 1
inside = count(matryoshka.child)
return inside + 1
Then now we return the number of people to our
first caller
28
Definitions
Recursion: process in which a function calls itself directly
Basic Concept
The basic concept of recursion is that a problem can be solved intuitively
and much easily if it is represented in one or more smaller versions.
What was the problem in the previous real life example?
What was the subproblem?
?
31
Basic components of recursion
● Base case The condition that signals when the function should stop
and return the final state.
● Recurrence relation Reduces all cases towards base case. The section
where the function calls itself with modified inputs and state
● State An identifier that fully locate which subproblem we are dealing
with currently
32
What were the Base Case, Recursive Relation and The State in the previous
example?
State
def count(matryoshka):
if matryoshka.empty:
return 1 Base Case
inside = count(matryoshka.child)
Recurrence
Relation
return inside + 1
33
Base case
● are known and simplest cases of the problem
● is a terminating scenario
● initiate the process of returning to the original call function (or
problem)
34
Recurrence Relation
● should be a breakdown of the current problem into smaller problems
● Is a set of cases reduces the current case towards base case
● It aggregates the result from recursive calls and returns the answer
we have so far to caller
35
State
● Identifies the current subproblem completely
● Helps locate which subproblem we are dealing with currently
● Changes in state should lead towards base cases
36
Sample Question
Solution
Q1. What happens if we continue to divide a number which is a power of four
by four?
● The final result will be 1
Q2. What if the number is not power of four?
● Their number will be below 1 eventually
From the above questions we can see that we have two base cases and the
state is the number that is divided by four every time we have recursive call.
Dividing a power of four by four
64 / 16 / 4 / 1
Dividing a random number by four
28 / 7 / 1.75 / 0.44
40
Implementation
def isPowerOfFour(self, n: int) -> bool:
# basecases
if n == 1: return True
if n < 1: return False
# function calling itself with change in state
return self.isPowerOfFour(n / 4)
Pair Programming
Fibonacci Number
42
What is the base case and recurrence relation for the above
problem?
43
Base Case
● If n == 1, return 1
● If n == 0, return 0
State
● n
Recurrence Relation
● f(n)=f(n-1)+f(n-2)
44
Implementation
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
return fib(n-1) + fib(n-2)
How does recursion work?
How does the recursion work?
● The system uses some ordering to execute the calls
● The ordering is done based on Last In First Out (depth first) order
● It uses stack
Stack.top -> currently executing
Stack.push -> calling function
Stack.pop -> returning a value
47
Example Using Call Stack
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=4
one = fib(3)
two = fib(2)
49
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
50
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1) N=2
two = fib(n-2) one = fib(1)
two = fib(0)
return one + two
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
51
def fib(n: int) -> int:
if n == 1:
return 1
N=1
return 1
if n == 0:
return 0
one = fib(n-1) N=2
two = fib(n-2) one = fib(1)
two = fib(0)
return one + two
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
52
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1) N=2
two = fib(n-2) one = 1
two = fib(0)
return one + two
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
53
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1) N=2
two = fib(n-2) one = 1
two = fib(0)
return one + two
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
54
def fib(n: int) -> int:
if n == 1:
return 1
N=0
return 0
if n == 0:
return 0
one = fib(n-1) N=2
two = fib(n-2) one = 1
two = fib(0)
return one + two
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
55
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1) N=2
two = fib(n-2) one = 1
two = 0
return one + two
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
56
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
N=2
one = fib(n-1) one = 1
two = fib(n-2) two = 0
return one + two
return one + two
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
57
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=3
one = 1
two = fib(1)
N=4
one = fib(3)
two = fib(2)
58
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=3
one = 1
two = fib(1)
N=4
one = fib(3)
two = fib(2)
59
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1) N=1
two = fib(n-2) return 1
return one + two
N=3
one = 1
two = fib(1)
N=4
one = fib(3)
two = fib(2)
60
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=3
one = 1
two = 1
return one + two
N=4
one = fib(3)
two = fib(2)
61
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=3
one = 1
two = 1
return one + two
N=4
one = fib(3)
two = fib(2)
62
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=4
one = 2
two = fib(2)
63
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=4
one = 2
two = fib(2)
64
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=2
one = fib(0)
two = fib(1)
N=4
one = 2
two = fib(2)
65
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1) N=0
two = fib(n-2) return 0
return one + two
N=2
one = fib(0)
two = fib(1)
N=4
one = 2
two = fib(2)
66
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=2
one = 0
two = fib(1)
N=4
one = 2
two = fib(2)
67
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=2
one = 0
two = fib(1)
N=4
one = 2
two = fib(2)
68
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1) N=1
two = fib(n-2) return 1
return one + two
N=2
one = 0
two = fib(1)
N=4
one = 2
two = fib(2)
69
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=2
one = 0
two = 1
N=4
one = 2
two = fib(2)
70
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=2
one = 0
two = 1
return one + two
N=4
one = 2
two = fib(2)
71
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=4
one = 2
two = 1
72
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
return one + two
N=4
one = 2
two = 1
return 3
73
Using Execution Tree
Step by Step Simulation
Go to this LINK
Depth of the
execution tree
Num of nodes
approximately
: 2n+1 - 1
76
Debugging using the execution tree
● Simulate the code using the
execution tree using pen and
paper by yourself.
● Simulate your code with
recursion simulation sites
● Compare the results
77
Debugging using the call stack and print statements
● Simulate the code
using the call stack N=0 def recursive_fn(state):
return 0 print(“state: “, state)
by yourself using
pen and paper N=2 .
● Use print one = 1 .
two = fib(0)
statements to see if .
your code matches N=3
one = fib(2) print(“result: “, result)
the simulation two = fib(1) return result
N=4
one = fib(3)
two = fib(2)
78
Complexity Analysis
Time Complexity
(Any suggestion?)
There are two parts
● Number of recursive calls
● The complexity of the function
81
How do we find the number of calls?
● Draw the execution tree
● Execution tree is a tree that describes the
flow of execution for the recursive function
● A node represents a call
● The total number of nodes is your final
answer
82
Fib(4)
Facts: 20+1 - 1 4
● Two branching on each
node 3 2
● Height of the tree is the
2 1 1 0
equal to n
● In a full binary tree with n
1 0
height, we have 2n+1 - 1 2
3+1
-1
nodes
83
In general
● Let
○ b = number of branches on each node
○ d = depth of the tree
d+1
● Total number of nodes = b -1
● After only taking the dominant term
(Num of branching) depth of the tree
84
What is the complexity of the function?
● This is plain time and space complexity
analysis of the inner working of the function
● Are we doing any costly operations in the
function?
85
In Summary
O(function) • O((Num of branching) depth of the tree )
Cost of running function Number of recursive calls
86
Space Complexity
Before we discuss the space complexity, let’s consider these questions
● How does the computer run recursion function?
● How does it know when to return and combine?
87
It uses call stack
88
What is the space complexity of the call
stack?
89
Maximum size of the stack which is maximum
depth of the execution tree
90
Did we forget anything?
91
What about the states and space complexity
of the function?
92
In Summary
N=0
return 0
State Size + function N=2
complexity one = 1
two = fib(0)
depth
[O(states’ size) + O(space complexity of function) ] •
O(maximum depth of the execution tree ) N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
93
Checklist (Ask your instructor, if anything is missing)
❏ You have to be very comfortable with writing out the base case and
recurrence relation of recursive function
❏ You have to be very comfortable with drawing execution tree of a recursion
function
❏ Understanding the execution order using the execution tree
❏ Comfortably analyze the time and space complexity of a recursive function
94
Recursion versus Iteration
Anything that can be done recursively can be done iteratively
● Iteration is faster and more space-efficient than recursion.
● Recursion has function call overhead
● Iterative programs are easy to debug
So why do we even need recursion?
● If the solution to the given problem is achieved by breaking it down into its
subproblems, the recursive approach is a more intuitive and natural way to
solve it.
95
Recognizing in Questions
● Look for patterns in the question that suggest that the same
operation is being applied to smaller versions of the same
problem.
● Look for base cases or stopping conditions that define when
the recursion should end.
● Searching problems with branching conditions
Things to pay attention
Stack Overflow ?
Occurs when the program uses up the memory assigned to it in the call stack
In the case of recursion we will mainly deal with two types:
- Memory Limit Exceeded
- Maximum Recursion Depth Exceeded.
98
Memory Limit Exceeded
is a type of stack overflow error that occurs
when the parameters of our function take too
much space.
99
Maximum Recursion Depth Exceeded
is a type of stack overflow error that occurs when
the number of recursive calls being stored in the
call stack is greater than allowed.
Different programming languages have different
maximum recursion Depth sizes:
- Python => 1000
- Javascript => 10,000
- Java => around 10,000
- C++ =>No Limit
100
A function with missing base case
def fib(n: int) -> int:
if n == 1:
return 1
What happens when n == 0?
return fib(n-1) +
fib(n-2)
101
Wrong recurrence relation
def numOfPeopleInQueue(n: int) -> int:
if n == 1:
return 1
prev = numOfPeopleInQueue(n + 1) + 1
return prev
102
Using list as state
def divideInBuckets(i, picked: List) -> int:
if i >= len(arr):
return 0
notPick = divideInBuckets(i+1,picked)
picked.append(arr[i])
pick = divideInBuckets(i+1,picked) Passed by
reference
return pick + notPick
103
Practice Questions
Reverse String
Count Good Numbers
Pascal’s Triangle II
Merge Two Sorted List
Decode String
Predict the Winner
Power of three
Power of four
Resources
● Leetcode Recursion I Card
● Leetcode Recursion II Card
Quote
“In order to understand recursion, one must first
understand recursion.”
Unknown