Recursion I(With Code) (1)
Recursion I(With Code) (1)
Recursion
1
Lecture Flow
1. Prerequisites
2. Motivation Problem
3. Basic Concepts
4. Time and Space Complexity
5. Common Pitfalls
6. Practice Questions and Resources
7. Quote of the Day
2
Prerequisites
3
Motivation Problem
Imagine you are in a long queue and the reception asks you how many
people there are in the line.
Each person can only talk to the person behind and infront of them.
4
Brainstorm
● Let’s assume each person in the queue
○ Can increment numbers by 1
○ Can ask other people
● What if I asked the person behind me “How many people are there behind
you ?” and whatever their response is, I can add myself and do a plus 1 and
inform the reception.
● And what if the person I asked, asks the person behind him “How many
people are there behind you ?” and whatever he gets, he adds himself and
do plus 1 and informs me.
•
•
•
5
1
How many
people are
there?
6
How many
people are
there?
2
How many
people are
there?
7
How many
people are
there?
3
How many How many
people are people are
there? there?
8
How many
people are
there?
4
How many How many
people are people are How many
there? there? people are
there?
9
How many
people are
there?
5
How many How many
people are people are
there? No one is
there?
behind me.
10
How many
people are
there?
5
How many How many
people are people are
there? No one is
there?
behind me.
Stopping
case
11
How many
people are
there?
Only Me
12
How many
people are
there?
7
How many There is one person
people are behind me, So...
there? including me it will
be Two
13
How many
people are
there?
Two
14
How many
people are
there?
9
There are two people
behind me, So...
including me it will be
Three
15
How many
people are
there?
10
Three
16
11
There are
three people
17
Can we simulate this with code?
18
Does this work?
def ask(person):
behind = ask(person + 1)
people = behind + 1
return people
19
What happens?
behind =
ask(person + 1) behind =
people = behind + ask(person + 1)
behind = ask(person
1 people = behind +
+ 1)
1
people = behind + 1
return people
return people
return people
behind = ask(person + 1)
people = behind + 1
return people
21
What happens?
if noMorePeople:
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
22
Real Life Example 2
Imagine some one give you a matryoshka and ask you to count the
number of dolls, how would you solve the problem?
23
Brainstorm
24
Brainstorm
25
Brainstorm
26
Brainstorm
27
Brainstorm
28
Brainstorm
Base Case
return 1
29
Brainstorm
return 1+1
Base Case
return 1
30
Brainstorm
return 1+2
return 2
Base Case
return 1
31
Brainstorm
return 1+3
return 3
return 2
Base Case
return 1
32
Brainstorm
return 1+4
return 4
return 3
return 2
Base Case
return 1
33
Brainstorm
return 5
return 4
return 3
return 2
Base Case
return 1
34
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
35
Does this work?
def count(matryoshka):
inside = count(matryoshka.child)
return inside + 1
36
What happens?
inside = count(matryoshka.child)
return inside + 1
inside = count(matryoshka.child)
return inside + 1
inside = count(matryoshka.child)
return inside + 1
• • •
def count(matryoshka):
if matryoshka.empty:
return 1
inside = count(matryoshka.child)
return inside + 1
38
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
39
Definitions
40
Recursion: process in which a function calls itself directly
41
Basic Concept
A problem can be solved intuitively and much more easily if it is
represented as an operation of it’s smaller versions.
42
Basic Components of Recursion
● State
● Base case
● Recurrence relation
43
State
44
Base Case
45
Recurrence Relation
47
?
def count(matryoshka):
if matryoshka.empty:
?
return 1
prev = count(matryoshka.child) ?
curr = prev + 1
return curr
48
State
def count(matryoshka):
if matryoshka.empty:
Base Case
return 1
return curr
49
Practice
50
What is the state, the base case and recurrence relation for
the above problem?
51
Base Case
● n == 1
State
● n
Recurrence Relation
● f(n)=n*f(n-1)
52
How does this work?
53
factorial(n) factorial of n gets called
54
factorial(n)
calls factorial(n-1)
factorial(n-1)
55
factorial(n)
factorial(n-1)
calls factorial(n-2)
factorial(n-2)
56
factorial(n)
factorial(n-1)
factorial(n-2)
57
factorial(n)
factorial(n-1)
factorial(n-2)
..
58
factorial(n)
factorial(n-1)
factorial(n-2)
..
.
59
factorial(n)
factorial(n-1)
factorial(n-2)
..
.
calls factorial(2)
factorial(2)
60
factorial(n)
factorial(n-1)
factorial(n-2)
..
.
factorial(2)
calls factorial(1)
factorial(1)
61
factorial(n)
factorial(n-1)
factorial(n-2)
..
.
factorial(2)
factorial(1) {basecase}
62
factorial(n)
factorial(n-1)
factorial(n-2)
..
.
factorial(2)
{returns 1}
factorial(1)
63
factorial(n)
factorial(n-1)
factorial(n-2)
..
. {returns 2 which is (2) * factorial(1)}
factorial(2)
{returns 1}
factorial(1)
64
factorial(n)
factorial(n-1)
factorial(n-2)
..
. . {returns 2 which is (2) * factorial(1)}
factorial(2)
{returns 1}
factorial(1)
65
factorial(n)
factorial(n-1)
factorial(n-2)
.. .
. . {returns 2 which is (2) * factorial(1)}
factorial(2)
{returns 1}
factorial(1)
66
factorial(n)
factorial(n-1)
factorial(n-2)
. .. .
. . {returns 2 which is (2) * factorial(1)}
factorial(2)
{returns 1}
factorial(1)
67
factorial(n)
factorial(n-1)
{returns factorial(n-2) which is (n-2) * factorial(n-3)}
factorial(n-2)
. .. .
. . {returns 2 which is (2) * factorial(1)}
factorial(2)
{returns 1}
factorial(1)
68
factorial(n)
{returns factorial(n-1) which is (n-1) * factorial(n-2)}
factorial(n-1)
{returns factorial(n-2) which is (n-2) * factorial(n-3)}
factorial(n-2)
. .. .
. . {returns 2 which is (2) * factorial(1)}
factorial(2)
{returns 1}
factorial(1)
69
{returns factorial(n) which is (n) * factorial(n-1)}
factorial(n)
{returns factorial(n-1) which is (n-1) * factorial(n-2)}
factorial(n-1)
{returns factorial(n-2) which is (n-2) * factorial(n-3)}
factorial(n-2)
. .. .
. . {returns 2 which is (2) * factorial(1)}
factorial(2)
{returns 1}
factorial(1)
70
Pair Programming
71
Solution
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.
72
Dividing a power of four by four
64 / 16 / 4 / 1
28 / 7 / 1.75 / 0.44
73
Implementation
74
Execution Tree
● Is a tree-like representation of all possible execution
paths of a program.
75
Using Execution Tree
76
Fibonacci Sequence
77
Fibonacci Sequence
78
Step by Step Simulation
Go to this LINK
79
Depth of the
execution tree
Num of nodes
approximately
: 2n+1 - 1
80
Debugging using the execution tree
81
How does recursion work?
82
How does the recursion work?
● The system uses some ordering to execute the calls
● The ordering is done based on Last In First Out order
● It uses stack
stack.top() -> currently executing
stack.push() -> calling function
stack.pop() -> returning a value
83
Example Using Call Stack
84
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = fib(3)
two = fib(2)
85
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
86
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
87
def fib(n: int) -> int:
if n == 1:
return 1
N=1
return 1
if n == 0:
return 0
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
88
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
89
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
90
def fib(n: int) -> int:
if n == 1:
return 1
N=0
return 0
if n == 0:
return 0
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
91
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
N=3
one = fib(2)
two = fib(1)
N=4
one = fib(3)
two = fib(2)
92
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)
93
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=3
one = 1
two = fib(1)
N=4
one = fib(3)
two = fib(2)
94
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=3
one = 1
two = fib(1)
N=4
one = fib(3)
two = fib(2)
95
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
N=3
one = 1
two = fib(1)
N=4
one = fib(3)
two = fib(2)
96
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = fib(3)
two = fib(2)
97
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = fib(3)
two = fib(2)
99
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = fib(2)
100
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = fib(2)
101
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = fib(2)
102
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
N=4
one = 2
two = fib(2)
103
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = fib(2)
104
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = fib(2)
105
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
N=4
one = 2
two = fib(2)
106
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = fib(2)
107
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = fib(2)
108
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = 1
109
def fib(n: int) -> int:
if n == 1:
return 1
if n == 0:
return 0
one = fib(n-1)
two = fib(n-2)
N=4
one = 2
two = 1
return 3
110
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 .
N=4
one = fib(3)
two = fib(2)
111
Complexity Analysis
112
Time Complexity
(Any suggestion?)
113
There are two parts
114
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
115
Facts:
● Two branching on each node
● Height of the tree is equal to n
● In a full binary tree with n height, we have 2n+1 - 1 nodes
20+1 - 1 4
3 2
Fib(4)
2 1 1 0
23+1 - 1 1 0
116
In general
● Let
○ b = number of branches on each noden (number of calls each function makes)
○ d = depth of the tree
d+1
● Total number of nodes = b -1
117
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?
118
In Summary
119
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?
120
It uses call stack
121
What is the space complexity of the call
stack?
122
Maximum size of the stack which is maximum
depth of the execution tree
123
Did we forget anything?
124
What about the states and space complexity
of the function?
125
In Summary
N=0
return 0
N=4
one = fib(3)
two = fib(2)
126
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
127
Draw The Execution Tree
Pow(x,n)
128
Draw The Execution Tree
(with two branches)
Pow(x,n)
129
Recursion versus Iteration
130
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
131
Common Pitfalls
132
Stack Overflow ?
Occurs when the program uses up the memory assigned to it in the call stack
133
Memory Limit Exceeded
134
Maximum Recursion Depth Exceeded
135
A function with no base case
def ask(person):
behind = ask(person + 1)
people = behind + 1
return people
136
A function with missing base case
138
Wrong recurrence relation
prev = numOfPeopleInQueue(n + 1) + 1
return prev
139
Practice Questions
Power of Two
Power of three
Pascal’s Triangle II
Remove Linked List Elements
Remove Nodes From Linked List
Merge Two Sorted List
Decode String
Reverse Linked List
Count Good Numbers
Flatten a Multilevel Doubly Linked List
Predict the Winner
140
Resources
● Leetcode Recursion I Card
● Leetcode Recursion II Card
● Recurrence Relations
141
Quote
“In order to understand recursion, one must first
understand recursion.”
Unknown
142