0% found this document useful (0 votes)
213 views

Dynamic Programming Guide Sample

The document discusses dynamic programming and provides examples of solving problems using recursive and iterative approaches. It explains the key steps to developing a recursive solution, including iterating over options, reducing the target, choosing an optimal solution, defining base cases, and returning a result. The examples covered include the Fibonacci sequence, coin change problem, and unique paths problem. It also contrasts top-down recursive and bottom-up iterative solutions and how to transition between the two approaches.

Uploaded by

rohithashwanth45
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
213 views

Dynamic Programming Guide Sample

The document discusses dynamic programming and provides examples of solving problems using recursive and iterative approaches. It explains the key steps to developing a recursive solution, including iterating over options, reducing the target, choosing an optimal solution, defining base cases, and returning a result. The examples covered include the Fibonacci sequence, coin change problem, and unique paths problem. It also contrasts top-down recursive and bottom-up iterative solutions and how to transition between the two approaches.

Uploaded by

rohithashwanth45
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 13

G IDE

D NAMIC

G AMMING

ATALYK AKASH
The a hor of D namic Programming Pa erns
Disclaimer

This ebook is licensed for your personal enjoyment only. This ebook may not be re-sold or given away
to other people. If you would like to share this book with another person, please purchase an
additional copy for each recipient. If you’re reading this book and did not purchase it, or it was not
purchased for your use only, then please purchase your own copy. Thank you for respecting the hard
work of this author.

The information contained within this eBook is strictly for educational purposes. If you wish to apply
the ideas contained in this eBook, you are taking full responsibility for your actions. The author has
made every effort to ensure the accuracy of the information within this book was correct at the time of
publication. The author does not assume and hereby disclaims any liability to any party for any loss,
damage, or disruption caused by errors or omissions, whether such errors or omissions result from
accident, negligence, or any other cause.

No part of this eBook may be reproduced or transmitted in any form or by any means, electronic or
mechanical, recording, or by any information storage and retrieval system, without written permission
from the author.

Ó Copyright 2021 Atalyk Akash. All Rights Reserved.


Contents
What is Dynamic Programming 3
Fibonacci Sequence 4
Coin Change 6
Unique Paths 10
How to solve Dynamic Programming Problems 13
Top-Down 13
How to come up with a recursive solution 14
Applying the methods above to solve examples iteratively 18
How to calculate the time complexity of a recursive solution 20
Memoization 23
Bottom-Up 29
How to come up with an iterative solution 30
Applying the methods above to solve examples iteratively 34
How to calculate the time complexity of the iterative solution 35
Top-Down vs. Bottom-Up 37
From Top-Down to Bottom-Up 37
Practicing Common Dynamic Programming Problems 49
Minimum Cost Climbing Stairs 49
Minimum Path Sum 54
Climbing Stairs 60
Burst Balloons 65
Longest Common Subsequence 72
Best Time to Buy and Sell Stock 78
Nim Game 84
How to come up with a recursive solution
Perform the following steps to understand how to come up with a recursive solution.

Step 1. Iterate Over Options


When we solve a problem recursively, for every sub-problem (state), we have different options
(directions) to choose to reach our target. We need to iterate over all possible options to find the
most optimal solution.

𝐹(𝑜𝑝𝑡𝑖𝑜𝑛 1)
𝐹(𝑛) → 𝐹(𝑜𝑝𝑡𝑖𝑜𝑛 1)

𝐹(𝑜𝑝𝑡𝑖𝑜𝑛 𝑘)
Fibonacci Sequence

𝐹(𝑙𝑎𝑠𝑡 𝑡𝑒𝑟𝑚)
𝐹(𝑐𝑢𝑟𝑟𝑒𝑛𝑡 𝑡𝑒𝑟𝑚) →
𝐹(𝑜𝑛𝑒 𝑏𝑒𝑓𝑜𝑟𝑒 𝑡ℎ𝑒 𝑙𝑎𝑠𝑡 𝑡𝑒𝑟𝑚)

There are two options to choose to obtain 𝑛!" term:


• the last term
• one before the last term

Coin Change Problem

𝐹(𝑢𝑠𝑒 𝑐𝑜𝑖𝑛 𝑤𝑖𝑡ℎ 𝑑𝑒𝑛𝑜𝑚𝑖𝑛𝑎𝑡𝑖𝑜𝑛 5)


𝐹(𝑡𝑎𝑟𝑔𝑒𝑡) → 𝐹 (𝑢𝑠𝑒 𝑐𝑜𝑖𝑛 𝑤𝑖𝑡ℎ 𝑑𝑒𝑛𝑜𝑚𝑖𝑛𝑎𝑡𝑖𝑜𝑛 3)
𝐹(𝑢𝑠𝑒 𝑐𝑜𝑖𝑛 𝑤𝑖𝑡ℎ 𝑑𝑒𝑛𝑜𝑚𝑖𝑛𝑎𝑡𝑖𝑜𝑛 1)

There are three options to choose to make the target amount:


• use the coin with denomination 5
• use the coin with denomination 3
• use the coin with denomination 1

Unique Paths

𝐹(𝑚𝑜𝑣𝑒 𝑢𝑝)
𝐹(𝑥, 𝑦) →
𝐹(𝑚𝑜𝑣𝑒 𝑙𝑒𝑓𝑡)

There are two options (directions) to choose to reach the target position:
• move up
• move left

14
Step 2. Reduce Target
Choosing every option, we reduce a target to a particular value, and we call a recursive function
again with the new updated target.

Fibonacci Sequence

𝐹(𝑛 − 1)
𝐹(𝑛) →
𝐹(𝑛 − 2)

• Choosing the last term 𝐹(𝑛 − 1) reduces the problem to find (𝑛 − 1)!" term.
• Choosing the one before the last term 𝐹(𝑛 − 2) reduces the problem to find (𝑛 − 2)!" term.

𝐹(1)
𝐹(2) →
𝐹(0)

Coin Change Problem

𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 5)
𝐹(𝑡𝑎𝑟𝑔𝑒𝑡) → 𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 3)
𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 1)

• Choosing the coin with denomination 5 reduces the problem to make the new target amount of
𝑡𝑎𝑟𝑔𝑒𝑡 − 5.
• Choosing the coin with denomination 3 reduces the problem to make the new target amount of
𝑡𝑎𝑟𝑔𝑒𝑡 − 3.
• Choosing the coin with denomination 1 reduces the problem to make the new target amount of
𝑡𝑎𝑟𝑔𝑒𝑡 − 1.

𝐹(11 − 5)
𝐹(11) → 𝐹 (11 − 3)
𝐹(11 − 1)
𝐹(6)
𝐹(11) → 𝐹 (8)
𝐹(10)

Unique Paths

15
𝐹(𝑥 − 1, 𝑦)
𝐹(𝑥, 𝑦) →
𝐹(𝑥, 𝑦 − 1)

• Choosing the option to move up reduces the problem to find the number of unique paths to
reach (𝑥 − 1, 𝑦).
• Choosing the option to move left reduces the problem to find the number of unique paths to
reach (𝑥, 𝑦 − 1).

𝐹(1,2)
𝐹(2,2) →
𝐹(2,1)

Step 3. Choose Optimal Solution


After coming from recursive calls, we choose an optimal solution among these calls and add the
value for the current state.

Fibonacci Sequence

Sum up two previous values.

𝐹(𝑛) = 𝐹(𝑛 − 1) + 𝐹(𝑛 − 2)

Coin Change Problem

Choose the option that leads to the most optimal solution and add one to use one coin for the current
state.

𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 5)
𝐹(𝑡𝑎𝑟𝑔𝑒𝑡) = 𝑚𝑖𝑛 I𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 3)J + 1
𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 1)

Unique Paths

Sum up both options to find the total number of distinct ways.

𝐹(𝑥, 𝑦) = 𝐹(𝑥 − 1, 𝑦) + 𝐹(𝑥, 𝑦 − 1)

16
Step 4. Base Case
When we reach a base case, we need to quit a recursive function. The base case can be a base state
or case when a sub-problem is no longer valid (in this case, when it returns the worst result).

Fibonacci Sequence

We already know that the first two Fibonacci numbers are 0 and 1 respectively.

𝐹(1) = 1

𝐹(0) = 0

1 int F(n) {
2 if (n == 0) {
3 // base case
4 }
5 if (n == 1) {
6 // base case
7 }
8 }

Coin Change

When 𝑡𝑎𝑟𝑔𝑒𝑡 equals 0 (we don’t need any coin to make 0) or less than 0, the function reaches the
base case.

𝐹(0) = 0

1 int F(target) {
2 if (target == 0) {
3 // base case
4 }
5 if (target < 0) {
6 // not valid
7 }
8 }

Unique Paths

When the position equals the destination cell or the position is out of bounds, the function reaches the
base case.

𝐹(0, 0) = 1

17
1 int F(x, y) {
2 if (x == 0 && y == 0) {
3 // base case
4 }
5 if (x < 0 || y < 0) {
6 // not valid
7 }
8 }

Step 5. Return Result


Return a result of a recursive function.

After reaching the end of a function, return a result.

return result

Applying the steps above to solve examples recursively

Recursive solution for the Fibonacci Sequence

1 int Fib(int n) {
2 if (n == 0) {
3 return 0; // step 4
4 }
5 if (n == 1) {
6 return 1; // step 4
7 }
8
9 int last = Fib(n-1); // step 1 & 2
10 int beforeLast = Fib(n-2); // step 1 & 2
11 int result = last + beforeLast; // step 3
12
13 return result; // step 5
14 }

Recursive solution for Coin Change Problem

1 int CoinChange(int target, vector<int> &coins) {


2 if (target == 0) {
3 return 0; // step 4
4 }
5 int result = inf;
6 for (int i = 0; i < coins.size(); ++i) { // step 1
7 if (coins[i] <= target) {

18
8 int substateSolution = CoinChange(target-coins[i], coins); // step 2
9 result = min(result, substateSolution+1); // step 3
10 }
11 }
12 return result; // step 5
13 }

Recursive solution for Unique Paths

1 int UniquePaths(int x, int y) {


2 if (x == 0 && y == 0) {
3 return 1; // step 4
4 }
5 if (x < 0 || y < 0) {
6 return 0; // step 4
7 }
8 int up = UniquePaths(x-1, y); // step 1 & 2
9 int left = UniquePaths(x, y-1); // step 1 & 2
10
11 int result = up + left; // step 3
12
13 return result; // step 5
14 }

The problem with these recursive solutions is the recalculation of sub-problems, which leads to slow
performance.

19
How to calculate the time complexity of a recursive solution
To calculate the time-complexity of a recursive solution, try to answer the following questions:
• How many times does a function call itself (𝑡)?
• How many times is a function being recursed (𝑘)?

Based on that, we can say that the time complexity of a plain recursive solution is exponential 𝑂(𝑡 # ).

If we draw a recursive tree, we can find the time complexity by summing up the number of nodes in
the tree.

Figure 8. The recursive tree.

Fibonacci Sequence

To find 𝑛!" Fibonacci number, we need to sum up two previous Fibonacci numbers.

𝐹(𝑛) = 𝐹(𝑛 − 1) + 𝐹(𝑛 − 2)

The function is being called two times:

• 𝐹(𝑛 − 1)
• 𝐹(𝑛 − 2)

Then it’s being recursed 𝑛 times from 𝑛 to 0, where 𝑛 is the depth of the recursive tree for the
function.

So, the time complexity of the plain recursive solution is 𝑂(2$ ).

Let’s calculate the time complexity of the function to see if the statement above is true.

Let’s denote 𝑇(𝑛) as a function to calculate the time complexity of the Fibonacci Sequence.

20
𝑇(𝑛) = 𝑇(𝑛 − 1) + 𝑇(𝑛 − 2) + 𝐶 ,

where 𝑇(𝑛 − 1) and 𝑇(𝑛 − 2) are the time complexities to find 𝑛 − 1!" Fibonacci and 𝑛 − 2!" Fibonacci
numbers respectively, and 𝐶 is the number of constant operations performed inside the recursive call.

For simplicity let’s assume that 𝑇(𝑛 − 2) ≤ 𝑇(𝑛 − 1).

𝑇(𝑛) = 2 × 𝑇(𝑛 − 1) + 𝐶

𝑇(𝑛 − 1) = 2 × 𝑇(𝑛 − 2) + 𝐶

𝑇(𝑛) = 2 × (2 × 𝑇(𝑛 − 2) + 𝐶) + 𝐶

𝑇(𝑛) = 22 × 𝑇(𝑛 − 2) + 3𝐶

𝑇(𝑛 − 2) = 2 × 𝑇(𝑛 − 3) + 𝐶

𝑇(𝑛) = 22 × (2 × 𝑇(𝑛 − 3) + 𝐶) + 3𝐶

𝑇(𝑛) = 23 × 𝑇(𝑛 − 3) + 7𝐶

𝑇(𝑛) = 2# × 𝑇(𝑛 − 𝑘) + (2# − 1) × 𝐶

𝑇(0) = 1

𝑛−𝑘 = 0

𝑛 = 𝑘

𝑇(𝑛) = 2$ × 𝑇(0) + (2$ − 1) × 𝐶 = 2$ + (2$ − 1) × 𝐶

The upper bound time complexity is 𝑂(2$ ).

Using the idea that 𝐹(𝑛) and 𝑇(𝑛) are calculated in the same way, we can find a tighter upper bound
time complexity. Approximate value for 𝐹(𝑛) is 𝑂(𝜙 $ ), where 𝜙 = (1 + √5)/2 ≈ 1.618.

Consequently,

𝑇(𝑛) = 𝑂(𝜙 $ ) = 𝑂((1 + √5)/2)$ ) = 𝑂(1.618$ )

21
Coin Change

The function is being called 𝑛 times:

• 𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 𝑐𝑜𝑖𝑛 1)
• 𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 𝑐𝑜𝑖𝑛 2)
• 𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 𝑐𝑜𝑖𝑛 3)
• …
• 𝐹(𝑡𝑎𝑟𝑔𝑒𝑡 − 𝑐𝑜𝑖𝑛 𝑛)

where 𝑛 is the number of coins.

Then it’s being recursed 𝑡𝑎𝑟𝑔𝑒𝑡 𝑎𝑚𝑜𝑢𝑛𝑡 times (worst case).

So, the time complexity of the plain recursive solution is 𝑂(𝑛!%&'(! %*+,$! ).

Unique Paths

The function is being called two times:

• 𝐹(𝑥 − 1, 𝑦)
• 𝐹(𝑥, 𝑦 − 1)

Then it’s being recursed 𝑛 + 𝑚 times, where 𝑛 is the number of rows and 𝑚 is the number of columns.

Why is the function being recursed 𝑛 + 𝑚 times?

For every recursive call, we move one step up, or one step left. In other words, to reach leaf nodes, we
need to exhaust total rows and total columns, meaning that the depth of our recursive tree will be 𝑛 +
𝑚.

22
Figure 9. The recursive tree for Unique Paths.

So, the time complexity of the plain recursive solution is 𝑂(2$-* ) ≈ 𝑂(2$ )..

All the recursive solutions above lead to exponential time complexity due to overlapping sub-
problems.

23

You might also like