Dynamic Programming
Dynamic Programming
and,
fib(0) = 0
fib(1) = 1
We can see that the above function fib() to find the nth fibonacci number is divided
into two subproblems fib(n-1) and fib(n-2) each one of which will be further divided
into subproblems and so on.
1
2
3
4
5
6
7
8
9
int fib(int n)
{
if (n <= 1)
return n;
return fib(n-1) + fib(n-2);
}
Below is the recursion tree for the recursive solution to find the N-th Fibonacci
number:
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1)
/ \ / \ / \
fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
/ \
fib(1) fib(0)
We can see that the function fib(3) is being called 2 times. If we would have stored
the value of fib(3), then instead of computing it again, we could have reused the old
stored value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int fib(int n)
{
// Declare an array to store Fibonacci numbers
int f[n+2]; // 1 extra to handle case, n = 0
int i;
return f[n];
}
There are two main properties of any problem which identifies a problem that it can
be solved using the dynamic programming approach:
7
8
int fib(int n)
if ( n <= 1 )
return n;
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1)
/ \ / \ / \
fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
/ \
fib(1) fib(0)
We can see that the function fib(3) is being called 2 times. If we would have
stored the value of fib(3), then instead of computing it again, we could have
reused the old stored value.
For example, the Shortest Path problem has the following optimal
substructure property:
If a node x lies in the shortest path from a source node u to destination
node v then the shortest path from u to v is combination of shortest path
from u to x and shortest path from x to v. The standard All Pair Shortest Path
algorithms like Floyd–Warshall and Bellman-Ford are typical examples of
Dynamic Programming.
On the other hand, the Longest Path problem doesn’t have the Optimal
Substructure property. Here, by Longest Path we mean the longest simple
path (path without cycle) between any two nodes. Consider the following
unweighted graph given in the CLRS book. There are two longest paths from
q to t: q->r->t and q->s->t. Unlike shortest paths, these longest paths do not
have the optimal substructure property. For example, the longest path q->r-
>t is not a combination of the longest path from q to r and longest path from r
to t, because the longest path from q to r is q->s->t->r and the longest path
from r to t is r->q->s->t.
Topic 3 : Overlapping Subproblems Property
1
2
3
4
5
6
7
8
/* simple recursive program for Fibonacci numbers */
int fib(int n)
{
if ( n <= 1 )
return n;
return fib(n-1) + fib(n-2);
}
fib(5)
/ \
fib(4) fib(3)
/ \ / \
/ \ / \ / \
fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
/ \
fib(1) fib(0)
C++
4
5
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <bits/stdc++.h>
#define NIL -1
int lookup[MAX];
void _initialize()
int i;
lookup[i] = NIL;
int fib(int n)
if (lookup[n] == NIL)
if (n <= 1)
lookup[n] = n;
else
Run
Java
Output:
C/C++
6
7
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
int fib(int n)
int f[n+1];
int i;
f[0] = 0; f[1] = 1;
return f[n];
// Driver Code
int main ()
int n = 9;
return 0;
Run
Java
Output:
Fibonacci number is 34
For example, the Shortest Path problem has following optimal substructure property:
If a node x lies in the shortest path from a source node u to destination node v then
the shortest path from u to v is combination of shortest path from u to x and shortest
path from x to v. The standard All Pair Shortest Path algorithms like Floyd -
Warshall and Bellman-Ford are typical examples of Dynamic Programming.
Let us consider a simple example of the 0-1 Knapsack Problem. The problem
states that given values and weight associated with N items. The task is to put these
items into a Knapsack of capacity W, such that the value of all items in the
Knapsack is maximum possible. You can either include a complete element or do
not include it, it is not allowed to add a fraction of an element.
For Example:
Optimal Substructure: To consider all subsets of items, there can be two cases for
every item: (1) the item is included in the optimal subset, (2) not included in the
optimal set.
Therefore, the maximum value that can be obtained from N items is the max of the
following two values.
1. Maximum value obtained by n-1 items and W weight (excluding nth item).
2. Value of nth item plus maximum value obtained by n-1 items and W minus the
weight of the nth item (including nth item).
If the weight of the nth item is greater than W, then the nth item cannot be included
and case 1 is the only possibility.
Overlapping Subproblems: Let us first look at the recursive solution to the above
problem:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// A Dynamic Programming based solution for
// 0-1 Knapsack problem
#include <bits/stdc++.h>
using namespace std;
// A utility function that returns maximum of two integers
int max(int a, int b) { return (a > b) ? a : b; }
// This function returns the maximum value that can be put
// in a knapsack of capacity W
int knapSack(int W, int wt[], int val[], int n)
{
int i, w;
int K[n + 1][W + 1];
// Build table K[][] in bottom up manner
for (i = 0; i <= n; i++) {
for (w = 0; w <= W; w++) {
if (i == 0 || w == 0)
K[i][w] = 0;
else if (wt[i - 1] <= w)
K[i][w] = max(val[i - 1] + K[i - 1][w - wt[i -
1]], K[i - 1][w]);
else
K[i][w] = K[i - 1][w];
}
}
return K[n][W];
}
Run
Java
Output:
220
Topic 5 : Solving a Dynamic Programming Problem
1. Overlapping Subproblems
2. Optimal Substructure Property
Steps to solve a DP
1) Identify if it is a DP problem
least parameters
Step 2 : Deciding the state DP problems are all about state and their transition.
This is the most basic step which must be done very carefully because the state
transition depends on the choice of state definition you make. So, let's see what do
we mean by the term "state".
State A state can be defined as the set of parameters that can uniquely identify a
certain position or standing in the given problem. This set of parameters should be
as small as possible to reduce state space.
So, our first step will be deciding a state for the problem after identifying that the
problem is a DP problem.
As we know DP is all about using calculated results to formulate the final result.
So, our next step will be to find a relation between previous states to reach the
current state.
Step 3 : Formulating a relation among the states This part is the hardest part of
for solving a DP problem and requires a lot of intuition, observation and practice.
Let's understand it by considering a sample problem
How to do it? So here the intuition comes into action. As we can only use 1, 3 or 5
to form a given number. Let us assume that we know the result for n = 1,2,3,4,5,6 ;
being termilogistic let us say we know the result for the
state (n = 1), state (n = 2), state (n = 3) ......... state (n = 6)
Now, we wish to know the result of the state (n = 7). See, we can only add 1, 3 and
5. Now we can get a sum total of 7 by the following 3 ways:
Now, think carefully and satisfy yourself that the above three cases are covering all
possible ways to form a sum total of 7;
In general,
state(n) = state(n-1) + state(n-3) + state(n-5)
So, our code will look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Returns the number of arrangements to
// form 'n'
int solve(int n)
{
// base case
if (n < 0)
return 0;
if (n == 0)
return 1;
return solve(n-1) + solve(n-3) + solve(n-5);
}
The above code seems exponential as it is calculating the same state again and
again. So, we just need to add a memoization.
Step 4 : Adding memoization or tabulation for the state This is the easiest part of
a dynamic programming solution. We just need to store the state answer so that next
time that state is required, we can directly use it from our memory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// initialize to -1
int dp[MAXN];
// this function returns the number of
// arrangements to form 'n'
int solve(int n)
{
// base case
if (n < 0)
return 0;
if (n == 0)
return 1;
// checking if already calculated
if (dp[n]!=-1)
return dp[n];
// storing the result and returning
return dp[n] = solve(n-1) + solve(n-3) + solve(n-5);
}
2. A binomial coefficient C(n, k) also gives the number of ways, disregarding order,
that k objects can be chosen from among n objects; more formally, the number
of k-element subsets (or k-combinations) of an n-element set.
Write a function that takes two parameters n and k and returns the value of Binomial
Coefficient C(n, k). For example, your function should return 6 for n = 4 and k = 2,
and it should return 10 for n = 5 and k = 2.
C(n, 0) = C(n, n) = 1
Overlapping Subproblems It should be noted that the above function computes the
same subproblems again and again. See the following recursion tree for n = 5 and k
= 2. The function C(3, 1) is called two times. For large values of n, there will be many
common subproblems.
C(5, 2)
/ \
C(4, 1) C(4, 2)
/ \ / \
/ \ / \ / \
/ \ / \ / \
Since same suproblems are called again, this problem has Overlapping
Subproblems property
Pseudo Code
// Returns value of Binomial Coefficient C(n, k)
int binomialCoeff(int n, int k)
{
int C[n+1][k+1]
jumps[0] = 0
More Examples:
Optimal Substructure Let arr[0..n-1] be the input array and L(i) be the length of the
LIS ending at index i such that arr[i] is the last element of the LIS.
Then, L(i) can be recursively written as:
L(i) = 1 + max( L(j) ) where 0 < j < i and arr[j] < arr[i]; or
L(i) = 1, if no such j exists.
To find the LIS for a given array, we need to return max(L(i)) where 0 < i < n.
Thus, we see the LIS problem satisfies the optimal substructure property as the main
problem can be solved using solutions to subproblems.
Overlapping Subproblems Considering the above implementation, following is
recursion tree for an array of size 4. lis(n) gives us the length of LIS for arr[ ].
lis(4)
/ | \
/ \ /
lis(1)
We can see that there are many subproblems which are solved again and again. So
this problem has Overlapping Substructure property and recomputation of the same
subproblems can be avoided by either using Memoization or Tabulation.
Pseudo Code
/* lis() returns the length of the longest increasing
subsequence in arr[ ] of size n */
int lis( int arr[], int n )
{
int lis[n]
lis[0] = 1
/* Compute optimized LIS values in bottom up manner */
for (int i = 1; i < n; i++ )
{
lis[i] = 1;
for (int j = 0; j < i; j++ )
if ( arr[i] > arr[j] && lis[i] < lis[j] + 1)
lis[i] = lis[j] + 1
}
// Return maximum value in lis[]
return *max_element(lis, lis+n)
}
Optimal Substructure: To consider all subsets of items, there can be two cases for
every item: (1) the item is included in the optimal subset, (2) not included in the
optimal set.
Therefore, the maximum value that can be obtained from n items is a max of the
following two values.
1. Maximum value obtained by n-1 items and W weight (excluding nth item).
2. Value of nth item plus maximum value obtained by n-1 items and W minus
weight of the nth item (including nth item).
If the weight of the nth item is greater than W, then the nth item cannot be included
and case 1 is the only possibility.
Overlapping Subproblems
In the following recursion tree, K() refers to knapSack().
The two parameters indicated in the following recursion tree are n and W.
/ \
/ \
K(2,2) K(2,1)
/ \ / \
/ \ / \
/ \ / \ / \
/ \ / \ / \
Recursion tree for Knapsack capacity 2 units and 3 items of 1 unit weight.
Since suproblems are evaluated again, this problem has Overlapping Subprolems
property. So the 0-1 Knapsack problem has both properties -
Pseudo Code
// Returns the maximum value that can be put in a knapsack of capacity W
int knapSack(int W, int wt[], int val[], int n)
{
int K[n+1][W+1]
// Build table K[][] in bottom up manner
for (i = 0; i <= n; i++)
{
for (w = 0; w <= W; w++)
{
if (i==0 || w==0)
K[i][w] = 0
else if (wt[i-1] <= w)
K[i][w] = max(val[i-1] + K[i-1][w-wt[i-1]], K[i-1][w])
else
K[i][w] = K[i-1][w]
}
}
return K[n][W]
}