Dynamic Programming in Python (original) (raw)

Dynamic Programming (DP) is a technique used to solve problems by breaking them into smaller subproblems and storing their results for later use.

Use of Dynamic Programming

In the recursion tree for the Fibonacci example, the same subproblems appear repeatedly. For instance, values such as F(2), F(3) and F(4) are recomputed multiple times. This repeated work causes the naive recursive solution to grow exponentially in time as n becomes larger.

here_fn_denotes_nth_term_of_the_fibonacci

Recursion Tree for F5 with Overlapping Subproblems

Dynamic Programming improves this by solving each subproblem once and reusing the stored result.

Approaches of Dynamic Programming (DP)

Dynamic Programming can be implemented using two main approaches:

Dynamic-Programming

Approaches of Dynamic Programming (DP)

1. Top-Down Approach (Memoization)

In the top-down approach, we start with the original problem and solve smaller subproblems recursively. The results of solved subproblems are stored so they can be reused when needed again.

2. Bottom-Up Approach (Tabulation)

In the bottom-up approach, we solve the smallest subproblems first and use their results to build the solution for larger subproblems.

To know more about DP, refer to this article When to Use Dynamic Programming (DP)

Example of Dynamic Programming (DP)

Let's understand Dynamic Programming using the Fibonacci Sequence, where each number is the sum of the previous two numbers. Fibonacci Sequence:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

The Fibonacci formula is:

F(n) = F(n - 1) + F(n - 2)

Where:

Brute Force Approach

A simple recursive solution calculates each Fibonacci number by repeatedly computing the previous two Fibonacci numbers.

Python `

def fib(n): if n <= 1: return n return fib(n - 1) + fib(n - 2)

print(fib(5))

`

Below is the recursion tree of the above recursive solution:

nth_term_of_fibonacci_series

nth term of fibonacci series

Using Memoization Approach – O(n) Time and O(n) Space

In the memoization approach, we use recursion along with a memoization array (memo) to store already calculated Fibonacci values. Before solving a subproblem, we first check whether its result is already available in the array. If it is available, we reuse it directly; otherwise, we calculate it and store the result for future use.

Python `

def fib_rec(n, memo): if n <= 1: return n

if memo[n] != -1:
    return memo[n]

memo[n] = fib_rec(n - 1, memo) + fib_rec(n - 2, memo)
return memo[n]

def fib(n): memo = [-1] * (n + 1) return fib_rec(n, memo)

n = 5 print(fib(n))

`

**Explanation:

Using Tabulation Approach – O(n) Time and O(n) Space

In the tabulation approach, we solve the problem in a bottom-up manner. We start with the known Fibonacci values and store them in a DP array. Then, we build the remaining Fibonacci numbers one by one using the previously computed values until we reach the required term.

Python `

def fib(n): dp = [0] * (n + 1)

dp[0] = 0
dp[1] = 1

for i in range(2, n + 1):
    dp[i] = dp[i - 1] + dp[i - 2]

return dp[n]

n = 5 print(fib(n))

`

**Explanation:

Using Space-Optimized Approach – O(n) Time and O(1) Space

In the tabulation approach, we store all Fibonacci values in a DP array. However, each Fibonacci number depends only on the previous two values. So, instead of storing the entire array, we can keep track of just the last two Fibonacci numbers and use them to calculate the next value.

Python `

def fib(n): if n <= 1: return n

prev2, prev1 = 0, 1

for _ in range(2, n + 1):
    curr = prev1 + prev2
    prev2 = prev1
    prev1 = curr

return prev1

n = 5 print(fib(n))

`

**Explanation:

Common Algorithms That Use DP