Professional Documents
Culture Documents
Nikola Otasevic
One of the reasons why I personally believe that DP questions might not be
the best way to test engineering ability is that they’re predictable and easy to
pattern match. They allow us to filter much more for preparedness as
opposed to an engineering quality.
These questions typically seem pretty complex on the outside and might give
you an impression that a person who solves them is very good at algorithms.
Similarly, people who may not be able to get over some mind-twisting
concepts of DP might seem pretty weak in their knowledge of algorithms.
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 1 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
In the rest of this post, I will go over a recipe that you can follow to figure out
if a problem is a “DP problem”, as well as to figure out a solution to such a
problem. Specifically, I will go through the following steps:
Sample DP Problem
Problem statement:
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 2 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
In this problem, we’re on a crazy jumping ball, trying to stop, while avoiding
spikes along the way.
1) You’re given a flat runway with a bunch of spikes in it. The runway is
represented by a boolean array which indicates if a particular (discrete) spot is
clear of spikes. It is True for clear and False for not clear.
3) Every time you land on a spot, you can adjust your speed by up to 1 unit
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 3 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
4) You want to safely stop anywhere along the runway (does not need to be at
the end of the array). You stop when your speed becomes 0. However, if you
land on a spike at any point, your crazy bouncing ball bursts and it’s a game
over.
Recognizing that a problem can be solved using DP is the first and often the
most difficult step in solving it. What you want to ask yourself is whether your
problem solution can be expressed as a function of solutions to similar
smaller problems.
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 4 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
In the case of our example problem, given a point on the runway, a speed, and
the runway ahead, we could determine the spots where we could potentially
jump next. Furthermore, it seems that whether we can stop from the current
point with the current speed depends only on whether we could stop from the
point we choose to go to next. That is a great thing because by moving
forward we shorten the runway ahead and make our problem smaller. We
should be able to repeat this process all the way until we get to a point where
it is obvious whether we can stop.
In our example, the two parameters that could change for every subproblem
are:
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 5 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
2. Speed (S)
One could say that the runway ahead is changing as well, but that would be
redundant considering that the entire non-changing runway and the position
(P) carry that information already.
Now, with these 2 changing parameters and other static parameters, we have
the complete description of our sub-problems.
Once you figure out that the recurrence relation exists and you specify the
problems in terms of parameters, this should come as a natural step. How do
problems relate to each other? In other words, let’s assume that you have
computed the subproblems. How would you compute the main problem?
Because you can adjust your speed by up to 1 before jumping to the next
position, there are only 3 possible speeds and therefore 3 spots in which we
could be next.
1.
1. (S, P + S); # if we do not change the speed
2. (S – 1, P + S – 1); # if we change the speed by -1
3. (S + 1, P + S + 1); # if we change the speed by +1
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 6 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
If we can find a way to stop in any of the subproblems above, then we can also
stop from (S, P). This is because we can transition from (S, P) to any of the
above three options.
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 7 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
In our example:
1. P < 0 || P >= length of runway seems like the right thing to do. An
alternative could be to consider making P == end of runway a base case.
However, it is possible that a problem splits into a subproblem which
goes beyond the end of the runway, so we really need to check for
inequality.
2. This seems pretty obvious. We can simply check if runway[P] is false.
3. Similar to #1, we could simply check for S < 0 and S == 0. However, here
we can reason that it is impossible for S to be < 0 because S decreases by
at most 1, so it would have to go through S == 0 case beforehand.
Therefore S == 0 is a sufficient base case for the S parameter.
Recursive Iterative
Asymptotic time Same assuming
Same
complexity memoization
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 8 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
Stack overflow issues are typically a deal breaker and a reason why you
would not want to have recursion in a (backend) production system. However,
for the purposes of the interview, as long as you mention the trade-offs, you
should typically be fine with either of the implementations. You should feel
comfortable implementing both.
def
canStopRecursive(runway,
initSpeed, startIndex = 0):
# negative base cases
need to go first
1if (startIndex
def canStopRecursive(runway,
>= initSpeed, startIndex = 0):
len(runway) or startIndex
< 0 or
2 initSpeed
# negative
< 0 or notbase cases need to go first
runway[startIndex]):
3#return ifFalse
(startIndex >= len(runway) or startIndex < 0 or
base case for a
stopping condition
4ifreturn
initSpeed == 0:
initSpeed < 0 or not runway[startIndex]):
True
# Try all possible paths
5for adjustedSpeed
return Falsein
[initSpeed, initSpeed - 1,
initSpeed + 1]:
6# Recurrence
# baserelation:
case for
If a stopping condition
you can stop from any of
the subproblems,
7# you ifcaninitSpeed
also stop == 0:
from the main problem
if canStopRecursive(
8 runway, return True
adjustedSpeed, startIndex
+ adjustedSpeed):
9 return#TrueTry all possible paths
return False
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 9 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
13 if canStopRecursive(
15 return True
16 return False
An iterative solution:
def
canStopIterative(runway,
initSpeed, startIndex = 0):
# maximum speed
cannot be larger than
length of def
the canStopIterative(runway,
runway. We initSpeed, startIndex = 0):
will talk about
1# making#this bound
maximum
tighter later on.
speed cannot be larger than length of the runway. We will talk
maxSpeedabout= len(runway)
if (startIndex >=
2
len(runway) or startIndex
# making
< 0 or initSpeed < 0 or this bound tighter later on.
initSpeed > maxSpeed or
3
not runway[startIndex]):
maxSpeed = len(runway)
return False
# {position i : set of
4
speeds for which we can
stop fromifposition
(startIndex
i} >= len(runway) or startIndex < 0 or initSpeed < 0 or initSpeed >
5memo = {}
# BasemaxSpeed
cases, we can or not runway[startIndex]):
stop when a position is
6 a spikereturn
not and speed is
False
zero.
for position in
7
range(len(runway)):
# {position i : set of speeds for which we can stop from position i}
if runway[position]:
memo[position] =
8
set([0])
memo = {}
# Outer loop to go over
positions from the last one
9the first#one
to Base cases, we can stop when a position is not a spike and speed is zero.
for position in
reversed(range(len(runwa
10
y))): for position in range(len(runway)):
# Skip positions which
contain spikes
11if not runway[position]:
if runway[position]:
continue
# For each position, go
12 all possible
over memo[position]
speeds = set([0])
for speed in range(1,
maxSpeed + 1):
13# Recurrence relation
is the same as in the
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 10 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
14 # Outer loop to go over positions from the last one to the first one
17 if not runway[position]:
18 continue
25 memo[position].add(speed)
26 break
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 11 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
1. Store your function result into your memory before every return
statement
2. Look up the memory for the function result before you start doing any
other computation
Here is the code from above with added memoization (added lines are
highlighted):
def
canStopRecursiveWithMe
mo(runway, initSpeed,
startIndex = 0, memo =
None):
1# Only defdonecanStopRecursiveWithMemo(runway,
the first time initSpeed, startIndex = 0, memo =
None):
to initialize the memo.
if memo == None:
2memo = {}
# Only
# First check if thedone
result the first time to initialize the memo.
exists in memo
3if startIndex in memo and
initSpeedif inmemo == None:
memo[startIndex]:
4return
memo[startIndex]memo = {}
[initSpeed]
5# negative base cases
need to go #first
First check if the result exists in memo
if (startIndex >=
6
len(runway) or startIndex
< 0 or if startIndex in memo and initSpeed in memo[startIndex]:
initSpeed < 0 or not
7
runway[startIndex]):
return memo[startIndex][initSpeed]
insertIntoMemo(memo,
startIndex,
8 initSpeed,
False)
# negative base cases need to go first
return False
9# base case
stopping condition
for a
if initSpeedif (startIndex
== 0: >= len(runway) or startIndex < 0 or
10 insertIntoMemo(memo,
startIndex, initSpeed,
True) initSpeed < 0 or not runway[startIndex]):
11 return True
# Try all possible paths
insertIntoMemo(memo,
for adjustedSpeed in startIndex, initSpeed, False)
12
[initSpeed, initSpeed - 1,
initSpeed + 1]:
# Recurrencereturn False
relation: If
13 can stop from any of
you
the subproblems,
# base case for a stopping condition
# you can also stop
14
from the main problem
if
if initSpeed == 0:
canStopRecursiveWithMe
mo(
runway,
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 12 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
16 return True
21 if canStopRecursiveWithMemo(
24 return True
26 return False
Time (s)
canStopRecursive 10.239
canStopIterative 0.021
canStopRecursiveWithMemo 0.008
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 13 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
You can see that the pure recursive approach takes about 500x more time
than the iterative approach and about 1300x more time than the recursive
approach with memoization. Note that this discrepancy would grow rapidly
with the length of the runway. I encourage you to try running it yourself.
The work done per each state is O(1) in this problem because, given all other
states, we simply have to look at 3 subproblems to determine the resulting
state.
As we noted in the code before, |S| is limited by length of the runway (|P|), so
we could say that the number of states is |P|^2 and because work done per
each state is O(1), then the total time complexity is O(|P|^2).
However, it seems that |S| can be further limited because if it were really |P| it
is very clear that stopping would not be possible because you would have to
jump the length of the entire runway on the first move.
So let’s see how we can put a tighter bound on |S|. Let’s call maximum speed
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 14 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
S. Assume that we’re starting from position 0. How quickly could we stop if
we were trying to stop as soon as possible and if we ignore potential spikes?
In the first iteration, we would have to come at least to the point (S-1), by
adjusting our speed at zero by -1. From there we would at a minimum go by (S-
2) steps forward, and so on.
=> S * (S – 1) / 2 < L
Considering that S – r2 > 0 for any S > 0 and L > 0, we need the following:
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 15 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
That means that the total time complexity depends only on the length of the
runway L in the following form:
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 16 of 17
Dynamic Programming: 7 Steps to Solve any DP Interview Problem 15/06/18, 1)50 PM
The 7 steps that we went through should give you a framework for
systematically solving any dynamic programming problem. I highly
recommend practicing this approach on a few more problems to perfect your
approach.
When you feel like you’ve conquered these ideas, check out Refdash where
you are interviewed by a senior engineer and get a detailed feedback on your
coding, algorithms, and system design.
http://blog.refdash.com/dynamic-programming-tutorial-example/ Page 17 of 17