Dynamic Programming (DP) Introduction (original) (raw)

Last Updated : 24 Dec, 2024

Dynamic Programming is a commonly used algorithmic technique used to optimize recursive solutions when same subproblems are called again.

When to Use Dynamic Programming (DP)?

Dynamic programming is used for solving problems that consists of the following characteristics:

1. Optimal Substructure:

The property Optimal substructure means that we use the optimal results of subproblems to achieve the optimal result of the bigger problem.

**Example:

Consider the problem of finding the **minimum cost path in a weighted graph from a **source node to a **destination node. We can break this problem down into smaller subproblems:

The solution to the larger problem (finding the minimum cost path from the source node to the destination node) can be constructed from the solutions to these smaller subproblems.

2. Overlapping Subproblems:

The same subproblems are solved repeatedly in different parts of the problem refer to Overlapping Subproblems Property in Dynamic Programming.

**Example:

Consider the problem of computing the **Fibonacci series. To compute the Fibonacci number at index **n, we need to compute the Fibonacci numbers at indices **n-1 and **n-2. This means that the subproblem of computing the Fibonacci number at index **n-2 is used twice (note that the call for n - 1 will make two calls, one for **n-2 and other for n-3) in the solution to the larger problem of computing the Fibonacci number at index **n.

You may notice overlapping subproblems highlighted in the second recursion tree for Nth Fibonacci diagram shown below.

Approaches of Dynamic Programming (DP)

Dynamic programming can be achieved using two approaches:

**1. Top-Down Approach (Memoization):

In the top-down approach, also known as **memoization, we keep the solution recursive and add a memoization table to avoid repeated calls of same subproblems.

**2. Bottom-Up Approach (Tabulation):

In the bottom-up approach, also known as **tabulation, we start with the **smallest subproblems and gradually build up to the final solution.

Dynamic-Programming

Please refer Tabulation vs Memoization for the detailed differences.

**Example of Dynamic Programming (DP)

**Example 1: Consider the problem of finding the Fibonacci sequence:

**Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

**Brute Force Approach: To find the nth Fibonacci number using a brute force approach, you would simply add the ****(n-1)th** and (n-2)th Fibonacci numbers.

C++ `

// C++ program to find // fibonacci number using recursion. #include <bits/stdc++.h> using namespace std;

// Function to find nth fibonacci number int fib(int n) { if (n <= 1) { return n; }

return fib(n - 1) + fib(n - 2);

}

int main() { int n = 5; cout << fib(n); return 0; }

C

// C program to find // fibonacci number using recursion. #include <stdio.h>

// Function to find nth fibonacci number int fib(int n) { if (n <= 1) { return n; } return fib(n - 1) + fib(n - 2); }

int main() { int n = 5; printf("%d", fib(n)); return 0; }

Java

// Java program to find // fibonacci number using recursion. class GfG {

// Function to find nth fibonacci number
static int fib(int n) {
    if (n <= 1) {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

public static void main(String[] args) {
    int n = 5;
    System.out.println(fib(n));
}

}

Python

Python program to find

fibonacci number using recursion.

Function to find nth fibonacci number

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

if name == "main": n = 5 print(fib(n))

C#

// C# program to find // fibonacci number using recursion. using System;

class GfG {

// Function to find nth fibonacci number
static int fib(int n) {
    if (n <= 1) {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

static void Main(string[] args) {
    int n = 5;
    Console.WriteLine(fib(n));
}

}

JavaScript

// JavaScript program to find // fibonacci number using recursion.

// Function to find nth fibonacci number function fib(n) { if (n <= 1) { return n; } return fib(n - 1) + fib(n - 2); }

//driver code let n = 5; console.log(fib(n));

`

Below is the **recursion tree of the above recursive solution.

fibonaci

The time complexity of the above approach is **exponential and upper bounded by **O(2 n ) as we make two recursive calls in every function.

How will Dynamic Programming (DP) Work?

Let’s us now see the above recursion tree with overlapping subproblems highlighted with same color. We can clearly see that that recursive solution is doing a lot work again and again which is causing the time complexity to be exponential. Imagine time taken for computing a large Fibonacci number.

1

**Using Memoization Approach - O(n) Time and O(n) Space

**To achieve this in our example we simply take an **memo array initialized to -1. As we make a recursive call, we first check if the value stored in the memo array corresponding to that position is -1. The value -1 indicates that we haven't calculated it yet and have to recursively compute it. The output must be stored in the memo array so that, next time, if the same value is encountered, it can be directly used from the memo array.

C++ `

// C++ program to find // fibonacci number using memoization. #include #include using namespace std;

int fibRec(int n, vector &memo) {

// Base case
if (n <= 1) {
    return n;
}

// To check if output already exists
if (memo[n] != -1) {
    return memo[n];
}

// Calculate and save output for future use
memo[n] = fibRec(n - 1, memo) + fibRec(n - 2, memo);

return memo[n];

}

int fib(int n) { vector memo(n + 1, -1); return fibRec(n, memo); }

int main() { int n = 5; cout << fib(n); return 0; }

Java

// Java program to find // fibonacci number using memoization. import java.util.Arrays;

class GfG {

static int fibRec(int n, int[] memo) {

    // Base case
    if (n <= 1) {
        return n;
    }

    // To check if output already exists
    if (memo[n] != -1) {
        return memo[n];
    }

    // Calculate and save output for future use
    memo[n] = fibRec(n - 1, memo) + fibRec(n - 2, memo);

    return memo[n];
}

static int fib(int n) {
    int[] memo = new int[n + 1];
    Arrays.fill(memo, -1);
    return fibRec(n, memo);
}

public static void main(String[] args) {
    int n = 5;
    System.out.println(fib(n));
}

}

Python

Python program to find

fibonacci number using memoization.

def fibRec(n, memo):

# Base case
if n <= 1:
    return n

# To check if output already exists
if memo[n] != -1:
    return memo[n]

# Calculate and save output for future use
memo[n] = fibRec(n - 1, memo) + \
          fibRec(n - 2, memo)
return memo[n]

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

n = 5 print(fib(n))

C#

// C# program to find // fibonacci number using memoization. using System; using System.Collections.Generic;

class GfG {

// Recursive function with memoization
static int FibRec(int n, List<int> memo) {
  
    // Base case
    if (n <= 1)
    {
        return n;
    }

    // To check if output already exists
    if (memo[n] != -1) {
        return memo[n];
    }

    // Calculate and save output for future use
    memo[n] = FibRec(n - 1, memo) + FibRec(n - 2, memo);

    return memo[n];
}

// Wrapper function to initiate the 
  // memoization process
static int Fib(int n) {
  
    // Initialize the memoization array with -1
    List<int> memo = new List<int>(new int[n + 1]);
    for (int i = 0; i <= n; i++) {
        memo[i] = -1;
    }
    return FibRec(n, memo);
}

static void Main() {
    int n = 5;
    Console.WriteLine(Fib(n)); 
}

}

JavaScript

// Javascript program to find // fibonacci number using memoization. function fibRec(n, memo) {

// Base case
if (n <= 1) {
    return n;
}

// To check if output already exists
if (memo[n] !== -1) {
    return memo[n];
}

// Calculate and save output for future use
memo[n] = fibRec(n - 1, memo) + 
          fibRec(n - 2, memo);

// Returning the final output
return memo[n];

}

function fib(n) {

// Initialize array with -1
const memo = new Array(n + 1).fill(-1);

// Call helper function
return fibRec(n, memo);

}

// Driver code const n = 5; console.log(fib(n));

`

**Using Tabulation Approach - O(n) Time and O(n) Space

In this approach, we use an array of size (n + 1), often called dp[], to store Fibonacci numbers. The array is initialized with base values at the appropriate indices, such as dp[0] = 0 and dp[1] = 1. Then, we iteratively calculate Fibonacci values from dp[2] to dp[n] by using the relation **dp[i] = dp[i-1] + dp[i-2]. This allows us to efficiently compute Fibonacci numbers in a loop. Finally, the value at dp[n] gives the Fibonacci number for the input n, as each index holds the answer for its corresponding Fibonacci number.

C++ `

// C++ program to find // fibonacci number using tabulation. #include #include using namespace std;

// Function for calculating the nth Fibonacci number int fibo(int n) { vector dp(n + 1);

// Storing the independent values in dp
dp[0] = 0;
dp[1] = 1;

// Using the bottom-up approach
for (int i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
}

return dp[n];

}

int main() { int n = 5; cout << fibo(n); return 0; }

C

// C program to find // fibonacci number using tabulation. #include <stdio.h>

// Function for calculating the nth Fibonacci number int fibo(int n) { int dp[n + 1];

// Storing the independent values in dp
dp[0] = 0;
dp[1] = 1;

// Using the bottom-up approach
for (int i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
}

return dp[n];

}

int main() { int n = 5; printf("%d", fibo(n)); return 0; }

Java

// Java program to find // fibonacci number using tabulation. import java.util.Arrays;

// Function for calculating the nth Fibonacci number class GfG { static int fibo(int n) { int[] dp = new int[n + 1];

    // Storing the independent values in dp
    dp[0] = 0;
    dp[1] = 1;

    // Using the bottom-up approach
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }

    return dp[n];
}

public static void main(String[] args) {
    int n = 5;
    System.out.println(fibo(n));
}

}

Python

Python program to find

fibonacci number using tabulation.

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

# Storing the independent values in dp
dp[0] = 0
dp[1] = 1

# Using the bottom-up approach
for i in range(2, n + 1):
    dp[i] = dp[i - 1] + dp[i - 2]

return dp[n]

n = 5 print(fibo(n))

C#

// C# program to find // fibonacci number using tabulation. using System;

class GfG { static int Fibo(int n) { int[] dp = new int[n + 1];

    // Storing the independent values in dp
    dp[0] = 0;
    dp[1] = 1;

    // Using the bottom-up approach
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    
    return dp[n];
}

static void Main() {
    int n = 5;
    Console.WriteLine(Fibo(n));
}

}

JavaScript

// JavaScript program to find // fibonacci number using tabulation.

function fibo(n) { let dp = new Array(n + 1);

// Storing the independent values in dp
dp[0] = 0;
dp[1] = 1;

// Using the bottom-up approach
for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
}

return dp[n];

}

//driver code let n = 5; console.log(fibo(n));

`

**Using Space Optimised Approach - O(n) Time and O(1) Space

In the above code, we can see that the current state of any fibonacci number depends only on the **previous two values. So we do not need to store the whole table of size n+1 but instead of that we can only store the previous two values.

C++ `

// C++ program to find // fibonacci number using space optimised. #include using namespace std;

int fibo(int n) {

int prevPrev, prev, curr;

// Storing the independent values
prevPrev = 0;
prev = 1;
curr = 1;

// Using the bottom-up approach
for (int i = 2; i <= n; i++) {
    curr = prev + prevPrev;
    prevPrev = prev;
    prev = curr;
}

return curr;

}

int main() { int n = 5; cout << fibo(n); return 0; }

C

// C program to find // fibonacci number using space optimised. #include <stdio.h>

int fibo(int n) { int prevPrev, prev, curr;

// Storing the independent values
prevPrev = 0;
prev = 1;
curr = 1;

// Using the bottom-up approach
for (int i = 2; i <= n; i++) {
    curr = prev + prevPrev;
    prevPrev = prev;
    prev = curr;
}

return curr;

}

int main() { int n = 5; printf("%d", fibo(n)); return 0; }

Java

// Java program to find // fibonacci number using space optimised. class GfG { static int fibo(int n) { int prevPrev, prev, curr;

    // Storing the independent values
    prevPrev = 0;
    prev = 1;
    curr = 1;

    // Using the bottom-up approach
    for (int i = 2; i <= n; i++) {
        curr = prev + prevPrev;
        prevPrev = prev;
        prev = curr;
    }

    return curr;
}

public static void main(String[] args) {
    int n = 5;
    System.out.println(fibo(n));
}

}

Python

Python program to find

fibonacci number using space optimised.

def fibo(n): prevPrev, prev, curr = 0, 1, 1

# Using the bottom-up approach
for i in range(2, n + 1):
    curr = prev + prevPrev
    prevPrev = prev
    prev = curr

return curr

n = 5 print(fibo(n))

C#

// C# program to find // fibonacci number using space optimised. using System;

class GfG { static int Fibo(int n) { int prevPrev = 0, prev = 1, curr = 1;

    // Using the bottom-up approach
    for (int i = 2; i <= n; i++) {
        curr = prev + prevPrev;
        prevPrev = prev;
        prev = curr;
    }

    return curr;
}

static void Main() {
    int n = 5;
    Console.WriteLine(Fibo(n));
}

}

JavaScript

// Javascript program to find // fibonacci number using space optimised. function fibo(n) { let prevPrev = 0, prev = 1, curr = 1;

// Using the bottom-up approach
for (let i = 2; i <= n; i++) {
    curr = prev + prevPrev;
    prevPrev = prev;
    prev = curr;
}

return curr;

}

let n = 5; console.log(fibo(n));

`

**Common Algorithms that Use DP:

Advantages of Dynamic Programming ****(DP)**

Dynamic programming has a wide range of advantages, including:

Applications of Dynamic Programming ****(DP)**

Dynamic programming has a wide range of applications, including:

Characteristics of Dynamic Programming Algorithm