0% found this document useful (0 votes)
23 views104 pages

Dynamic Programming Algos With Coding

Uploaded by

Anuraaga Nath
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views104 pages

Dynamic Programming Algos With Coding

Uploaded by

Anuraaga Nath
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 104

Dynamic Programming is an algorithmic approach to solve some complex problems easily and save

time and number of comparisons by storing the results of past computations. The basic idea of
dynamic programming is to store the results of previous calculation and reuse it in future instead of
recalculating them.

We can also see Dynamic Programming as dividing a particular problem into subproblems and then
storing the result of these subproblems to calculate the result of the actual problem.

Consider the problem to find the N-th Fibonacci number.

We know that n-th fibonacci number fib(n) can be defined as:

fib(n) = fib(n-1) + fib(n-2), where n >= 2.

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.

The first few Fibonacci numbers are:

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

The recursive program to find N-th Fibonacci number is shown below:

CPP

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.

The time complexity of the recursive solution is exponential. However, we can improve the time
complexity by using Dynamic Programming approach and storing the results of the subproblems as
shown below:

CPP

int fib(int n)

// Declare an array to store Fibonacci numbers

int f[n+2]; // 1 extra to handle case, n = 0

int i;

// 0th and 1st number of the series are 0 and 1


f[0] = 0;

f[1] = 1;

for (i = 2; i <= n; i++)

// Add the previous 2 numbers in the series

// and store it

f[i] = f[i-1] + f[i-2];

return f[n];

The time complexity of the above solution is linear.


Overlapping Subproblems Property

We had already discussed the basics of Overlapping Subproblems property of a problem that can be
solved using Dynamic Programming algorithm. Let us extend our previous example of Fibonacci
Number to discuss the overlapping subproblems property in details.

/* simple recursive program for Fibonacci numbers */

int fib(int n)

if ( n <= 1 )

return n;

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

Recursion tree for execution of fib(5)

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 already discussed how storing results of the subproblems can be effective in reducing the
number of calculations or operations to obtain the final result. As in the above recursion tree, we can
see that different values like fib(1), fib(0), fib(2) are being calculated more than once. There are two
different ways to store the values so that these values can be reused:
1. Memoization (Top Down)

2. Tabulation (Bottom Up)

Let us look at each one of these in details:

1. Memoization (Top Down): The memoized program for a problem is similar to the recursive
version with a small modification that it looks into a lookup table before computing
solutions. We initialize a lookup array with all initial values as NIL. Whenever we need the
solution to a subproblem, we first look into the lookup table. If the precomputed value is
there then we return that value, otherwise, we calculate the value and put the result in the
lookup table so that it can be reused later. Following is the memoized version for nth
Fibonacci Number.

C++Java

/* C++ program for Memoized version

for nth Fibonacci number */

#include <bits/stdc++.h>

using namespace std;

#define NIL -1

#define MAX 100

int lookup[MAX];

/* Function to initialize NIL

values in lookup table */

void _initialize()

int i;

for (i = 0; i < MAX; i++)

lookup[i] = NIL;

/* Function for nth Fibonacci number */


int fib(int n)

if (lookup[n] == NIL)

if (n <= 1)

lookup[n] = n;

else

lookup[n] = fib(n - 1) + fib(n - 2);

return lookup[n];

// Driver code

int main ()

int n = 40;

_initialize();

cout << "Fibonacci number is " << fib(n);

return 0;

Output:

Fibonacci number is 102334155

1. Tabulation (Bottom Up): The tabulated program for a given problem builds a table in bottom
up fashion and returns the last entry from table. For example, for the same Fibonacci
number, we first calculate fib(0) then fib(1) then fib(2) then fib(3) and so on. So literally, we
are building the solutions of subproblems bottom-up. Following is the tabulated version for
nth Fibonacci Number.

C/C++Java

/* C++ program for Tabulated version */

#include<bits/stdc++.h>
int fib(int n)

int f[n+1];

int i;

f[0] = 0; f[1] = 1;

for (i = 2; i <= n; i++)

f[i] = f[i-1] + f[i-2];

return f[n];

// Driver Code

int main ()

int n = 9;

printf("Fibonacci number is %d ", fib(n));

return 0;

Output:

Fibonacci number is 34

Both Tabulated and Memoized approaches stores the solutions of subproblems. In Memoized
version, the table is filled on demand while in Tabulated version, starting from the first entry, all
entries are filled one by one. Unlike the Tabulated version, all entries of the lookup table are not
necessarily filled in Memoized version.

Mark as Read

Report An IssueIf you are facing any i


Optimal Substructure Property

A given problem has Optimal Substructure Property if the optimal solution of the given problem can
be obtained by using optimal solutions of its subproblems.

That is, say if a problem x is divided into subproblems A and B then the optimal solution of x can be
obtained by summing up the optimal solutions to the subproblems A and B.

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 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:

value[] = {60, 100, 120}


weight[] = {10, 20, 30}
W = 50

Where, value[] is the array containing values of elements,


weight[] is the array containing corresponding weights.
and, W is the weight of Knapsack.

The answer will be 220. We will pick the 2nd and 3rd elements
and add them to the Knapsack for maximum value.

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:

// This function returns the maximum value that can


// be put in a knapsack of capacity W
int knapSack(int W, int weight[], int value[], int n)
{
// Base Case
if (n == 0 || W == 0)
return 0;

// If the weight of the nth item is more than Knapsack


// capacity W, then this item cannot be included in
// the optimal solution
if (wt[n-1] > W)
return knapSack(W, wt, val, n-1);

// Return the maximum of two cases:


// (1) nth item included
// (2) not included
else return max( val[n-1] + knapSack(W-wt[n-1], wt, val, n-1),
knapSack(W, wt, val, n-1)
);
}

It should be noted that the above function computes the same subproblems again and again. See the
following recursion tree when the above recursive function is evaluated with the sample examples.

In the following recursion tree, K() refers to knapSack(). The two


parameters indicated in the following recursion tree are n and W.
The recursion tree is for following sample inputs.
W = 2, wt[] = {1, 1, 1}, val[] = {10, 20, 30}

K(3, 2) ---------> K(n, W)


/ \
/ \
K(2, 2) K(2, 1)
/ \ / \
/ \ / \
K(1, 2) K(1, 1) K(1, 1) K(1, 0)
/ \ / \ / \
/ \ / \ / \
K(0, 2) K(0, 1) K(0, 1) K(0, 0) K(0, 1) K(0, 0)

Recursion tree for Knapsack capacity 2 units a


nd 3 items of 1 unit weight.

Since sub-problems are evaluated again, this problem has Overlapping Subproblems property. So the
0-1 Knapsack problem has both properties of a dynamic programming problem. Like other typical
Dynamic Programming(DP) problems, recomputations of same subproblems can be avoided by
constructing a temporary array K[][] in a bottom-up manner. Following is Dynamic Programming
based implementation.

C++Java

// 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];

// Driver Code

int main()

int val[] = { 60, 100, 120 };

int wt[] = { 10, 20, 30 };

int W = 50;

int n = sizeof(val) / sizeof(val[0]);

cout << knapSack(W, wt, val, n);

return 0;

Output

220

Mark as Read

Report An IssueIf you are facing any i


Solving a Dynamic Programming Problem

Dynamic Programming (DP) is a technique that solves some particular type of problems in
Polynomial Time. Dynamic Programming solutions are faster than exponential brute method and can
be easily proved for their correctness. Before we study how to think Dynamically for a problem, we
need to learn:

1. Overlapping Subproblems

2. Optimal Substructure Property

Steps to solve a DP

1) Identify if it is a DP problem

2) Decide a state expression with

least parameters

3) Formulate state relationship

4) Do tabulation (or add memoization)

Step 1 : How to classify a problem as a Dynamic Programming Problem?

• Typically, all the problems that require to maximize or minimize certain quantity or counting
problems that say to count the arrangements under certain condition or certain probability
problems can be solved by using Dynamic Programming.

• All dynamic programming problems satisfy the overlapping subproblems property and most
of the classic dynamic problems also satisfy the optimal substructure property. Once, we
observe these properties in a given problem, be sure that it can be solved using DP.

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. For example: In
our famous Knapsack problem, we define our state by two parameters index and weight i.e
DP[index][weight]. Here DP[index][weight] tells us the maximum profit it can make by taking items
from range 0 to index having the capacity of sack to be weight. Therefore, here the
parameters index and weight together can uniquely identify a subproblem for the knapsack
problem. 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

Given 3 numbers {1, 3, 5}, we need to tell


the total number of ways we can form a number 'N'

using the sum of the given three numbers.

(allowing repetitions and different arrangements).

Total number of ways to form 6 is: 8

1+1+1+1+1+1

1+1+1+3

1+1+3+1

1+3+1+1

3+1+1+1

3+3

1+5

5+1

Let's think dynamically about this problem. So, first of all, we decide a state for the given problem.
We will take a parameter n to decide state as it can uniquely identify any subproblem. So, our state
dp will look like state(n). Here, state(n) means the total number of arrangements to form n by using
{1, 3, 5} as elements. Now, we need to compute state(n). 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: 1) Adding 1 to all
possible combinations of state (n = 6) Eg : [ (1+1+1+1+1+1) + 1] [ (1+1+1+3) + 1] [ (1+1+3+1) + 1] [
(1+3+1+1) + 1] [ (3+1+1+1) + 1] [ (3+3) + 1] [ (1+5) + 1] [ (5+1) + 1] 2) Adding 3 to all possible
combinations of state (n = 4); Eg : [(1+1+1+1) + 3] [(1+3) + 3] [(3+1) + 3] 3) Adding 5 to all possible
combinations of state(n = 2) Eg : [ (1+1) + 5] Now, think carefully and satisfy yourself that the above
three cases are covering all possible ways to form a sum total of 7; Therefore, we can say that result
for state(7) = state (6) + state (4) + state (2) or state(7) = state (7-1) + state (7-3) + state (7-5) In
general, state(n) = state(n-1) + state(n-3) + state(n-5) So, our code will look like:

CPP

// 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 Adding memoization to the above code

CPP

// 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);

}
Another way is to add tabulation and make solution iterative.

Mark as Read

Report An IssueIf you are facing any i


Tabulation vs Memoization

There are two different ways to store the values so that the values of a sub-problem can be reused.
Here, will discuss two patterns of solving dynamic programming (DP) problems:

1. Tabulation: Bottom Up

2. Memoization: Top Down

Before getting to the definitions of the above two terms consider the following statements:

• Version 1: I will study the theory of DP from GeeksforGeeks, then I will practice some
problems on classic DP and hence I will master DP.

• Version 2: To Master DP, I would have to practice Dynamic problems and practice problems -
Firstly, I would have to study some theories of DP from GeeksforGeeks

Both versions say the same thing, the difference simply lies in the way of conveying the message and
that's exactly what Bottom-Up and Top-Down DP do. Version 1 can be related to Bottom-Up DP and
Version-2 can be related as Top-Down DP.

Tabulation Method - Bottom Up Dynamic Programming

As the name itself suggests starting from the bottom and accumulating answers to the top. Let's
discuss in terms of state transition.

Let's describe a state for our DP problem to be dp[x] with dp[0] as base state and dp[n] as our
destination state. So, we need to find the value of destination state i.e dp[n].
If we start our transition from our base state i.e dp[0] and follow our state transition relation to reach
our destination state dp[n], we call it the Bottom-Up approach as it is quite clear that we started our
transition from the bottom base state and reached the topmost desired state.

Now, Why do we call it the tabulation method?

To know this let's first write some code to calculate the factorial of a number using a bottom-up
approach. Once, again as our general procedure to solve a DP we first define a state. In this case, we
define a state as dp[x], where dp[x] is to find the factorial of x.

Now, it is quite obvious that dp[x+1] = dp[x] * (x+1)

// Tabulated version to find factorial x.

int dp[MAXN];
// base case

int dp[0] = 1;

for (int i = 1; i< =n; i++)

dp[i] = dp[i-1] * i;

The above code clearly follows the bottom-up approach as it starts its transition from the bottom-
most base case dp[0] and reaches its destination state dp[n]. Here, we may notice that the DP table
is being populated sequentially and we are directly accessing the calculated states from the table
itself and hence, we call it the tabulation method.

Memoization Method - Top-Down Dynamic Programming

Once, again let's describe it in terms of state transition. If we need to find the value for some state
say dp[n] and instead of starting from the base state that i.e dp[0] we ask our answer from the states
that can reach the destination state dp[n] following the state transition relation, then it is the top-
down fashion of DP.

Here, we start our journey from the top most destination state and compute its answer by taking in
count the values of states that can reach the destination state, till we reach the bottom-most base
state.

Once again, let's write the code for the factorial problem in the top-down fashion

// Memoized version to find factorial x.

// To speed up we store the values

// of calculated states

// initialized to -1

int dp[MAXN]

// return fact x!

int solve(int x)

if (x==0)
return 1;

if (dp[x]!=-1)

return dp[x];

return (dp[x] = x * solve(x-1));

As we can see we are storing the most recent cache up to a limit so that if next time we got a call
from the same state we simply return it from the memory. So, this is why we call it memoization as
we are storing the most recent state values.

In this case, the memory layout is linear that's why it may seem that the memory is being filled in a
sequential manner like the tabulation method, but you may consider any other top-down DP having
2D memory layout like Min Cost Path, here the memory is not filled in a sequential manner.

Mark as Read

Report An IssueIf you are facing any i


Sample Problems on Dynamic Programming

Problem #1 : Binomial Coefficients

Description -
Following are common definition of Binomial Coefficients -

1. A binomial coefficient C(n, k) can be defined as the coefficient of X^k in the expansion of (1 +
X)^n.

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.

Optimal Substructure

The value of C(n, k) can be recursively calculated using following standard formula for Binomial
Coefficients.

C(n, k) = C(n-1, k-1) + C(n-1, k)


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 an 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)
/ \ / \
C(3, 0) C(3, 1) C(3, 1) C(3, 2)
/ \ / \ / \
C(2, 0) C(2, 1) C(2, 0) C(2, 1) C(2, 1) C(2, 2)
/ \ / \ / \
C(1, 0) C(1, 1) C(1, 0) C(1, 1) C(1, 0) C(1, 1)

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]

// Caculate value of Binomial Coefficient in bottom up manner


for (i = 0; i <= n; i++)
{
for (j = 0; j <= min(i, k); j++)
{
// Base Cases
if (j == 0 || j == i)
C[i][j] = 1

// Calculate value using previously stored values


else
C[i][j] = C[i-1][j-1] + C[i-1][j]
}
}
return C[n][k]
}

Problem #2 : Minimum number of jumps to reach end

Given an array of integers where each element represents the max number of steps that can be
made forward from that element. Write a function to return the minimum number of jumps to reach
the end of the array (starting from the first element).

If an element is 0, then cannot move through that element.

Example

Input: arr[] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9}


Output: 3 (1-> 3 -> 8 ->9)
First element is 1, so can only go to 3. Second element is 3, so can make at most 3 steps eg to 5 or 8
or 9.

Solution -

we build a jumps[ ] array from left to right such that jumps[ i ] indicates the minimum number of
jumps needed to reach arr[ i ] from arr[ 0 ]. Finally, we return jumps[ n-1 ].

Pseudo Code

// Returns minimum number of jumps


// to reach arr[n-1] from arr[0]
int minJumps(int arr[], int n)
{
// jumps[n-1] will hold the result
int jumps[n]
if (n == 0 || arr[0] == 0)
return INT_MAX;

jumps[0] = 0

// Find the minimum number of jumps to reach arr[i]


// from arr[0], and assign this value to jumps[i]
for (i = 1; i < n; i++)
{
jumps[i] = INT_MAX
for (j = 0; j < i; j++)
{
if (i <= j + arr[j] && jumps[j] != INT_MAX)
{
jumps[i] = min(jumps[i], jumps[j] + 1)
break
}
}
}
return jumps[n-1]
}
Problem #3 : Longest Increasing Subsequence

Description-

The Longest Increasing Subsequence (LIS) problem is to find the length of the longest subsequence of
a given sequence such that all elements of the subsequence are sorted in increasing order. For
example, the length of LIS for {10, 22, 9, 33, 21, 50, 41, 60, 80} is 6 and LIS is {10, 22, 33, 50, 60, 80}.

More Examples:

Input : arr[] = {3, 10, 2, 1, 20}


Output : Length of LIS = 3
The longest increasing subsequence is 3, 10, 20

Input : arr[] = {3, 2}


Output : Length of LIS = 1
The longest increasing subsequences are {3} and {2}

Input : arr[] = {50, 3, 10, 7, 40, 80}


Output : Length of LIS = 4
The longest increasing subsequence is {3, 7, 40, 80}

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(3) lis(2) lis(1)
/ \ /
lis(2) lis(1) lis(1)
/
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 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)
}

Problem #4 : 0-1 Knapsack Problem

Description -

Given weights and values of n items, put these items in a knapsack of capacity W to get the
maximum total value in the knapsack.

In other words, given two integer arrays val[0..n-1] and wt[0..n-1] which represent values and
weights associated with n items respectively. Also given an integer W which represents knapsack
capacity, find out the maximum value subset of val[] such that sum of the weights of this subset is
smaller than or equal to W. You cannot break an item, either pick the complete item, or don't pick it
(0-1 property).

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 weight of 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.
The recursion tree is for following sample inputs.
wt[] = {1, 1, 1}, W = 2, val[] = {10, 20, 30}

K(3, 2) ---------> K(n, W)


/ \
/ \
K(2,2) K(2,1)
/ \ / \
/ \ / \
K(1,2) K(1,1) K(1,1) K(1,0)
/ \ / \ / \
/ \ / \ / \
K(0,2) K(0,1) K(0,1) K(0,0) K(0,1) K(0,0)
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]
}

Mark as Read

Report An IssueIf you are facing any i


Longest Common Subsequence (Part 1)

LCS Problem Statement: Given two sequences, find the length of longest subsequence present in
both of them. A subsequence is a sequence that appears in the same relative order, but not
necessarily contiguous. For example, "abc", "abg", "bdf", "aeg", '"acefg", .. etc are subsequences of
"abcdefg".

In order to find out the complexity of brute force approach, we need to first know the number of
possible different subsequences of a string with length n, i.e., find the number of subsequences with
lengths ranging from 1,2,..n-1. Recall from theory of permutation and combination that number of
combinations with 1 element are nC1. Number of combinations with 2 elements are nC2 and so forth
and so on. We know that nC0 + nC1 + nC2 + ... nCn = 2n. So a string of length n has 2n-1 different possible
subsequences since we do not consider the subsequence with length 0. This implies that the time
complexity of the brute force approach will be O(n * 2n). Note that it takes O(n) time to check if a
subsequence is common to both the strings. This time complexity can be improved using dynamic
programming.

It is a classic computer science problem, the basis of diff (a file comparison program that outputs the
differences between two files), and has applications in bioinformatics.

Examples:
LCS for input Sequences "ABCDGH" and "AEDFHR" is "ADH" of length 3.
LCS for input Sequences "AGGTAB" and "GXTXAYB" is "GTAB" of length 4.

The naive solution for this problem is to generate all subsequences of both given sequences and find
the longest matching subsequence. This solution is exponential in term of time complexity. Let us see
how this problem possesses both important properties of a Dynamic Programming (DP) Problem.

1) Optimal Substructure:
Let the input sequences be X[0..m-1] and Y[0..n-1] of lengths m and n respectively. And let L(X[0..m-
1], Y[0..n-1]) be the length of LCS of the two sequences X and Y. Following is the recursive definition
of L(X[0..m-1], Y[0..n-1]).

• If last characters of both sequences match (or X[m-1] == Y[n-1]) then


L(X[0..m-1], Y[0..n-1]) = 1 + L(X[0..m-2], Y[0..n-2])

• If last characters of both sequences do not match (or X[m-1] != Y[n-1]) then
L(X[0..m-1], Y[0..n-1]) = MAX ( L(X[0..m-2], Y[0..n-1]), L(X[0..m-1], Y[0..n-2]) )

Examples:
1) Consider the input strings "AGGTAB" and "GXTXAYB". Last characters match for the strings. So
length of LCS can be written as:
L("AGGTAB", "GXTXAYB") = 1 + L("AGGTA", "GXTXAY")
2) Consider the input strings "ABCDGH" and "AEDFHR. Last characters do not match for the strings.
So length of LCS can be written as:
L(“ABCDGH”, “AEDFHR”) = MAX ( L(“ABCDG”, “AEDFHR”), L(“ABCDGH”, “AEDFH”) )
So the LCS problem has optimal substructure property as the main problem can be solved using
solutions to subproblems.

2) Overlapping Subproblems:
Following is simple recursive implementation of the LCS problem. The implementation simply follows
the recursive structure mentioned above.

C++Java

/* A Naive recursive implementation of LCS problem */

#include <bits/stdc++.h>

using namespace std;

/* Returns length of LCS for X[0..m-1], Y[0..n-1] */

int lcs( char *X, char *Y, int m, int n )

if (m == 0 || n == 0)

return 0;

if (X[m-1] == Y[n-1])

return 1 + lcs(X, Y, m-1, n-1);

else

return max(lcs(X, Y, m, n-1), lcs(X, Y, m-1, n));


}

/* Driver code */

int main()

char X[] = "AGGTAB";

char Y[] = "GXTXAYB";

int m = strlen(X);

int n = strlen(Y);

cout<<"Length of LCS is "<< lcs( X, Y, m, n ) ;

return 0;

Output

Length of LCS is 4

Time complexity of the above naive recursive approach is O(2^n) in worst case and worst case
happens when all characters of X and Y mismatch i.e., length of LCS is 0.

Considering the above implementation, following is a partial recursion tree for input strings "AXYT"
and "AYZX"

In the above partial recursion tree, lcs("AXY", "AYZ") is being solved twice. If we draw the complete
recursion tree, then we can see that there are many subproblems which are solved again and again.
So this problem has Overlapping Substructure property and recomputation of same subproblems can
be avoided by either using Memoization or Tabulation.

Following is a Memoization implementation for the LCS problem.

C++Java

/* A Top-Down DP implementation of LCS problem */

#include <bits/stdc++.h>

using namespace std;

/* Returns length of LCS for X[0..m-1], Y[0..n-1] */

int lcs(char* X, char* Y, int m, int n,

vector<vector<int> >& dp)

if (m == 0 || n == 0)

return 0;

if (X[m - 1] == Y[n - 1])

return dp[m][n] = 1 + lcs(X, Y, m - 1, n - 1, dp);

if (dp[m][n] != -1) {

return dp[m][n];

return dp[m][n] = max(lcs(X, Y, m, n - 1, dp),

lcs(X, Y, m - 1, n, dp));

/* Driver code */

int main()

char X[] = "AGGTAB";

char Y[] = "GXTXAYB";

int m = strlen(X);
int n = strlen(Y);

vector<vector<int> > dp(m + 1, vector<int>(n + 1, -1));

cout << "Length of LCS is " << lcs(X, Y, m, n, dp);

return 0;

Output

Length of LCS is 4

Time Complexity : O(mn) ignoring recursion stack space

Mark as Read

Report An IssueIf you are facing any i


Longest Common Subsequence (Part 2)

LCS Problem Statement: Given two sequences, find the length of longest subsequence present in
both of them. A subsequence is a sequence that appears in the same relative order, but not
necessarily contiguous. For example, "abc", "abg", "bdf", "aeg", '"acefg", .. etc are subsequences of
"abcdefg".

In order to find out the complexity of brute force approach, we need to first know the number of
possible different subsequences of a string with length n, i.e., find the number of subsequences with
lengths ranging from 1,2,..n-1. Recall from theory of permutation and combination that number of
combinations with 1 element are nC1. Number of combinations with 2 elements are nC2 and so forth
and so on. We know that nC0 + nC1 + nC2 + ... nCn = 2n. So a string of length n has 2n-1 different possible
subsequences since we do not consider the subsequence with length 0. This implies that the time
complexity of the brute force approach will be O(n * 2n). Note that it takes O(n) time to check if a
subsequence is common to both the strings. This time complexity can be improved using dynamic
programming.

It is a classic computer science problem, the basis of diff (a file comparison program that outputs the
differences between two files), and has applications in bioinformatics.

Examples:
LCS for input Sequences "ABCDGH" and "AEDFHR" is "ADH" of length 3.
LCS for input Sequences "AGGTAB" and "GXTXAYB" is "GTAB" of length 4.

The naive solution for this problem is to generate all subsequences of both given sequences and find
the longest matching subsequence. This solution is exponential in term of time complexity. Let us see
how this problem possesses both important properties of a Dynamic Programming (DP) Problem.

1) Optimal Substructure:
Let the input sequences be X[0..m-1] and Y[0..n-1] of lengths m and n respectively. And let L(X[0..m-
1], Y[0..n-1]) be the length of LCS of the two sequences X and Y. Following is the recursive definition
of L(X[0..m-1], Y[0..n-1]).

• If last characters of both sequences match (or X[m-1] == Y[n-1]) then


L(X[0..m-1], Y[0..n-1]) = 1 + L(X[0..m-2], Y[0..n-2])

• If last characters of both sequences do not match (or X[m-1] != Y[n-1]) then
L(X[0..m-1], Y[0..n-1]) = MAX ( L(X[0..m-2], Y[0..n-1]), L(X[0..m-1], Y[0..n-2]) )

Examples:
1) Consider the input strings "AGGTAB" and "GXTXAYB". Last characters match for the strings. So
length of LCS can be written as:
L("AGGTAB", "GXTXAYB") = 1 + L("AGGTA", "GXTXAY")
2) Consider the input strings "ABCDGH" and "AEDFHR. Last characters do not match for the strings.
So length of LCS can be written as:
L(“ABCDGH”, “AEDFHR”) = MAX ( L(“ABCDG”, “AEDFHR”), L(“ABCDGH”, “AEDFH”) )
So the LCS problem has optimal substructure property as the main problem can be solved using
solutions to subproblems.

2) Overlapping Subproblems:
Following is simple recursive implementation of the LCS problem. The implementation simply follows
the recursive structure mentioned above.

C++Java

/* A Naive recursive implementation of LCS problem */

#include <bits/stdc++.h>

using namespace std;

/* Returns length of LCS for X[0..m-1], Y[0..n-1] */

int lcs( char *X, char *Y, int m, int n )

if (m == 0 || n == 0)

return 0;

if (X[m-1] == Y[n-1])

return 1 + lcs(X, Y, m-1, n-1);

else

return max(lcs(X, Y, m, n-1), lcs(X, Y, m-1, n));

}
/* Driver code */

int main()

char X[] = "AGGTAB";

char Y[] = "GXTXAYB";

int m = strlen(X);

int n = strlen(Y);

cout<<"Length of LCS is "<< lcs( X, Y, m, n ) ;

return 0;

Output

Length of LCS is 4

Time complexity of the above naive recursive approach is O(2^n) in worst case and worst case
happens when all characters of X and Y mismatch i.e., length of LCS is 0.

Considering the above implementation, following is a partial recursion tree for input strings "AXYT"
and "AYZX"

lcs("AXYT", "AYZX")
/
lcs("AXY", "AYZX") lcs("AXYT", "AYZ")
/ /
lcs("AX", "AYZX") lcs("AXY", "AYZ") lcs("AXY", "AYZ") lcs("AXYT", "AY")

In the above partial recursion tree, lcs("AXY", "AYZ") is being solved twice. If we draw the complete
recursion tree, then we can see that there are many subproblems which are solved again and again.
So this problem has Overlapping Substructure property and recomputation of same subproblems can
be avoided by either using Memoization or Tabulation.

Following is a Memoization implementation for the LCS problem.


C++Java

/* A Top-Down DP implementation of LCS problem */

#include <bits/stdc++.h>

using namespace std;

/* Returns length of LCS for X[0..m-1], Y[0..n-1] */

int lcs(char* X, char* Y, int m, int n,

vector<vector<int> >& dp)

if (m == 0 || n == 0)

return 0;

if (X[m - 1] == Y[n - 1])

return dp[m][n] = 1 + lcs(X, Y, m - 1, n - 1, dp);

if (dp[m][n] != -1) {

return dp[m][n];

return dp[m][n] = max(lcs(X, Y, m, n - 1, dp),

lcs(X, Y, m - 1, n, dp));

/* Driver code */

int main()

char X[] = "AGGTAB";

char Y[] = "GXTXAYB";

int m = strlen(X);

int n = strlen(Y);

vector<vector<int> > dp(m + 1, vector<int>(n + 1, -1));

cout << "Length of LCS is " << lcs(X, Y, m, n, dp);


return 0;

Output

Length of LCS is 4

Time Complexity : O(mn) ignoring recursion stack space

Following is a tabulated implementation for the LCS problem.

C++Java

/* Dynamic Programming C implementation of LCS problem */

#include <bits/stdc++.h>

using namespace std;

/* Returns length of LCS for X[0..m-1], Y[0..n-1] */

int lcs(char *X, char *Y, int m, int n)

// intitalizing a matrix of size (m+1)*(n+1)

int L[m + 1][n + 1];

/* Following steps build L[m+1][n+1] in bottom up fashion. Note

that L[i][j] contains length of LCS of X[0..i-1] and Y[0..j-1] */

for (int i = 0; i <= m; i++)

for (int j = 0; j <= n; j++)

if (i == 0 || j == 0)

L[i][j] = 0;

else if (X[i - 1] == Y[j - 1])

L[i][j] = L[i - 1][j - 1] + 1;


else

L[i][j] = max(L[i - 1][j], L[i][j - 1]);

// L[m][n] contains length of LCS for X[0..n-1] and Y[0..m-1]

return L[m][n];

// Driver program to test above function

int main()

char X[] = "AGGTAB";

char Y[] = "GXTXAYB";

int m = strlen(X);

int n = strlen(Y);

cout << "Length of LCS is: " << lcs(X, Y, m, n);

return 0;

Output

Length of LCS is 4

Time Complexity of the above implementation is O(mn) which is much better than the worst-case
time complexity of Naive Recursive implementation.
Auxiliary Space: O(m * n)

Mark as Read
Coin Change

Given a value sum, if we want to make change for sum cents, and we have an infinite supply of each
of coins[] = { coins1, coins2, .. , coinsn} valued coins, how many ways can we make the change? The
order of coins doesn't matter.

Examples:

Input: sum = 4, coins[] = {1,2,3},


Output: 4
Explanation: there are four solutions: {1, 1, 1, 1}, {1, 1, 2}, {2, 2}, {1, 3}.

Input: sum = 10, coins[] = {2, 5, 3, 6}


Output: 5
Explanation: There are five solutions:
{2,2,2,2,2}, {2,2,3,3}, {2,2,6}, {2,3,5} and {5,5}.

1) Optimal Substructure
To count the total number of solutions, we can divide all set solutions into two sets.

• Solutions that do not contain nth coin (or S[n-1]).

• Solutions that contain at least one S[n-1].

Let count(coins[], n, sum) be the function to count the number of solutions, then it can be written as
sum of count(coins[], n-1, sum) and count(coins[], n, sum-coins[n-1]).
Therefore, the problem has optimal substructure property as the problem can be solved using
solutions to subproblems.

2) Overlapping Subproblems
Following is a simple recursive implementation of the Coin Change problem. The implementation
simply follows the recursive structure mentioned above.

3) Approach (Algorithm)

See, here each coin of a given denomination can come an infinite number of times. (Repetition
allowed), this is what we call UNBOUNDED KNAPSACK. We have 2 choices for a coin of a particular
denomination, either i) to include, or ii) to exclude. But here, the inclusion process is not for just
once; we can include any denomination any number of times until sum<0.

Basically, If we are at coins[n-1], we can take as many instances of that coin ( unbounded inclusion )
i.e count(coins, n, sum - coins[n-1] ); then we move to coins[n-2]. After moving to coins[n-2], we
can't move back and can't make choices for coins[n-1] i.e count(coins, n-1, sum ).

Finally, as we have to find the total number of ways, so we will add these 2 possible choices,
i.e count(coins, n, sum - coins[n-1] ) + count(coins, n-1, sum ); which will be our required answer.

C++Java

// Recursive C++ program for

// coin change problem.

#include <bits/stdc++.h>
using namespace std;

// Returns the count of ways we can

// sum coins[0...n-1] coins to get sum "sum"

int count(int coins[], int n, int sum)

// If sum is 0 then there is 1 solution

// (do not include any coin)

if (sum == 0)

return 1;

// If sum is less than 0 then no

// solution exists

if (sum < 0)

return 0;

// If there are no coins and sum

// is greater than 0, then no

// solution exist

if (n <= 0)

return 0;

// count is sum of solutions (i)

// including coins[n-1] (ii) excluding coins[n-1]

return count(coins, n - 1, sum) +

count(coins, n, sum - coins[n - 1]);

// Driver code

int main()
{

int i, j;

int coins[] = { 1, 2, 3 };

int n = sizeof(coins) / sizeof(coins[0]);

int sum = 4;

cout << " " << count(coins, n, sum);

return 0;

Output

Time Complexity: O(2sum)

Space Complexity: O(target) - Auxiliary stack space

It should be noted that the above function computes the same subproblems again and again. See the
following recursion tree for coins[] = {1, 2, 3} and n = 5.

The function C({1}, 3) is called two times. If we draw the complete tree, then we can see that there
are many subproblems being called more than once.

C() --> count()

C({1,2,3}, 5)

/ \

/ \

C({1,2,3}, 2) C({1,2}, 5)

/ \ / \

/ \ / \

C({1,2,3}, -1) C({1,2}, 2) C({1,2}, 3) C({1}, 5)

/ \ / \ / \

/ \ / \ / \

C({1,2},0) C({1},2) C({1,2},1) C({1},3) C({1}, 4) C({}, 5)

/\ /\ /\ / \

/ \ / \ / \ / \
. . . . . . C({1}, 3) C({}, 4)

/\

/ \

. .

Since same subproblems are called again, this problem has Overlapping Subproblems property. So
the Coin Change problem has both properties (see this and this) of a dynamic programming problem.
Like other typical Dynamic Programming(DP) problems, recomputations of same subproblems can be
avoided by constructing a temporary array table[][] in bottom up manner.

Dynamic Programming Solution

C++Java

// C++ program for coin change problem.

#include <bits/stdc++.h>

using namespace std;

int count(int coins[], int n, int sum)

int i, j, x, y;

// We need sum+1 rows as the table

// is constructed in bottom up

// manner using the base case 0

// value case (sum = 0)

int table[sum + 1][n];

// Fill the entries for 0

// value case (sum = 0)

for (i = 0; i < n; i++)

table[0][i] = 1;

// Fill rest of the table entries

// in bottom up manner
for (i = 1; i < sum + 1; i++) {

for (j = 0; j < n; j++) {

// Count of solutions including coins[j]

x = (i - coins[j] >= 0) ? table[i - coins[j]][j]

: 0;

// Count of solutions excluding coins[j]

y = (j >= 1) ? table[i][j - 1] : 0;

// total count

table[i][j] = x + y;

return table[sum][n - 1];

// Driver Code

int main()

int coins[] = { 1, 2, 3 };

int n = sizeof(coins) / sizeof(coins[0]);

int sum = 4;

cout << count(coins, n, sum);

return 0;

// This code is contributed

// by Akanksha Rai(Abby_akku)

Output

4
Time Complexity: O(n * sum)
Following is a simplified version of method 2. The auxiliary space required here is O(sum) only.

C++Java

int count(int coins[], int n, int sum)

// table[i] will be storing the number of solutions for

// value i. We need sum+1 rows as the table is constructed

// in bottom up manner using the base case (sum = 0)

int table[sum + 1];

// Initialize all table values as 0

memset(table, 0, sizeof(table));

// Base case (If given value is 0)

table[0] = 1;

// Pick all coins one by one and update the table[]

// values after the index greater than or equal to the

// value of the picked coin

for (int i = 0; i < n; i++)

for (int j = coins[i]; j <= sum; j++)

table[j] += table[j - coins[i]];

return table[sum];

Output:

Thanks to Rohan Laishram for suggesting this space optimized version.


Please write comments if you find anything incorrect, or you want to share more information about
the topic discussed above.

Following is another Top Down DP Approach using memoization:

C++Java
#include <bits/stdc++.h>

using namespace std;

int coinchange(vector<int>& a, int v, int n,

vector<vector<int> >& dp)

if (v == 0)

return dp[n][v] = 1;

if (n == 0)

return 0;

if (dp[n][v] != -1)

return dp[n][v];

if (a[n - 1] <= v) {

// Either Pick this coin or not

return dp[n][v] = coinchange(a, v - a[n - 1], n, dp)

+ coinchange(a, v, n - 1, dp);

else // We have no option but to leave this coin

return dp[n][v] = coinchange(a, v, n - 1, dp);

int32_t main()

int tc = 1;

// cin >> tc;

while (tc--) {

int n, v;

n = 3, v = 4;

vector<int> a = { 1, 2, 3 };

vector<vector<int> > dp(n + 1,

vector<int>(v + 1, -1));

int res = coinchange(a, v, n, dp);


cout << res << endl;

Output

Time Complexity: O(M*sum)


Auxiliary Space: O(M*sum)

Mark as Read

Report An IssueIf you are facing any i


Edit Distance Problem

Given two strings str1 and str2 and below operations that can be performed on str1. Find minimum
number of edits (operations) required to convert 'str1' into 'str2'.

1. Insert

2. Remove

3. Replace

All of the above operations are of equal cost.

Examples:

Input: str1 = "geek", str2 = "gesek"


Output: 1
We can convert str1 into str2 by inserting a 's'.

Input: str1 = "cat", str2 = "cut"


Output: 1
We can convert str1 into str2 by replacing 'a' with 'u'.

Input: str1 = "sunday", str2 = "saturday"


Output: 3
Last three and first characters are same. We basically
need to convert "un" to "atur". This can be done using
below three operations.
Replace 'n' with 'r', insert t, insert a

What are the subproblems in this case?


The idea is to process all characters one by one starting from either from left or right sides of both
strings.
Let us traverse from right corner, there are two possibilities for every pair of character being
traversed.

m: Length of str1 (first string)


n: Length of str2 (second string)

1. If last characters of two strings are same, nothing much to do. Ignore last characters and get
count for remaining strings. So we recur for lengths m-1 and n-1.

2. Else (If last characters are not same), we consider all operations on 'str1', consider all three
operations on last character of first string, recursively compute minimum cost for all three
operations and take minimum of three values.

1. Insert: Recur for m and n-1

2. Remove: Recur for m-1 and n


3. Replace: Recur for m-1 and n-1

Below is implementation of above Naive recursive solution.

C++Java

// A Naive recursive C++ program to find minimum number

// operations to convert str1 to str2

#include <bits/stdc++.h>

using namespace std;

// Utility function to find minimum of three numbers

int min(int x, int y, int z) { return min(min(x, y), z); }

int editDist(string str1, string str2, int m, int n)

// If first string is empty, the only option is to

// insert all characters of second string into first

if (m == 0)

return n;

// If second string is empty, the only option is to

// remove all characters of first string

if (n == 0)

return m;

// If last characters of two strings are same, nothing

// much to do. Ignore last characters and get count for

// remaining strings.

if (str1[m - 1] == str2[n - 1])

return editDist(str1, str2, m - 1, n - 1);

// If last characters are not same, consider all three

// operations on last character of first string,


// recursively compute minimum cost for all three

// operations and take minimum of three values.

return 1

+ min(editDist(str1, str2, m, n - 1), // Insert

editDist(str1, str2, m - 1, n), // Remove

editDist(str1, str2, m - 1,

n - 1) // Replace

);

// Driver code

int main()

// your code goes here

string str1 = "sunday";

string str2 = "saturday";

cout << editDist(str1, str2, str1.length(),

str2.length());

return 0;

Output

The time complexity of above solution is exponential. In worst case, we may end up doing O(3m)
operations. The worst case happens when none of characters of two strings match. Below is a
recursive call diagram for worst case.
We can see that many subproblems are solved, again and again, for example, eD(2, 2) is called three
times. Since same subproblems are called again, this problem has Overlapping Subproblems
property. So Edit Distance problem has both properties (see this and this) of a dynamic programming
problem. Like other typical Dynamic Programming(DP) problems, recomputations of same
subproblems can be avoided by constructing a temporary array that stores results of subproblems

C++Java

// A Dynamic Programming based C++ program to find minimum

// number operations to convert str1 to str2

#include <bits/stdc++.h>

using namespace std;

// Utility function to find the minimum of three numbers

int min(int x, int y, int z) { return min(min(x, y), z); }

int editDistDP(string str1, string str2, int m, int n)

// Create a table to store results of subproblems

int dp[m + 1][n + 1];

// Fill d[][] in bottom up manner


for (int i = 0; i <= m; i++) {

for (int j = 0; j <= n; j++) {

// If first string is empty, only option is to

// insert all characters of second string

if (i == 0)

dp[i][j] = j; // Min. operations = j

// If second string is empty, only option is to

// remove all characters of second string

else if (j == 0)

dp[i][j] = i; // Min. operations = i

// If last characters are same, ignore last char

// and recur for remaining string

else if (str1[i - 1] == str2[j - 1])

dp[i][j] = dp[i - 1][j - 1];

// If the last character is different, consider

// all possibilities and find the minimum

else

dp[i][j]

=1

+ min(dp[i][j - 1], // Insert

dp[i - 1][j], // Remove

dp[i - 1][j - 1]); // Replace

return dp[m][n];

}
// Driver code

int main()

// your code goes here

string str1 = "sunday";

string str2 = "saturday";

cout << editDistDP(str1, str2, str1.length(),

str2.length());

return 0;

Output

Time Complexity: O(m x n)


Auxiliary Space: O(m x n)

Space Complex Solution: In the above-given method we require O(m x n) space. This will not be
suitable if the length of strings is greater than 2000 as it can only create 2D array of 2000 x 2000. To
fill a row in DP array we require only one row the upper row. For example, if we are filling the i = 10
rows in DP array we require only values of 9th row. So we simply create a DP array of 2 x str1 length.
This approach reduces the space complexity. Here is the C++ implementation of the above-
mentioned problem

C++Java

// A Space efficient Dynamic Programming

// based C++ program to find minimum

// number operations to convert str1 to str2

#include <bits/stdc++.h>

using namespace std;

void EditDistDP(string str1, string str2)

int len1 = str1.length();


int len2 = str2.length();

// Create a DP array to memoize result

// of previous computations

int DP[2][len1 + 1];

// To fill the DP array with 0

memset(DP, 0, sizeof DP);

// Base condition when second string

// is empty then we remove all characters

for (int i = 0; i <= len1; i++)

DP[0][i] = i;

// Start filling the DP

// This loop run for every

// character in second string

for (int i = 1; i <= len2; i++) {

// This loop compares the char from

// second string with first string

// characters

for (int j = 0; j <= len1; j++) {

// if first string is empty then

// we have to perform add character

// operation to get second string

if (j == 0)

DP[i % 2][j] = i;

// if character from both string

// is same then we do not perform any

// operation . here i % 2 is for bound


// the row number.

else if (str1[j - 1] == str2[i - 1]) {

DP[i % 2][j] = DP[(i - 1) % 2][j - 1];

// if character from both string is

// not same then we take the minimum

// from three specified operation

else {

DP[i % 2][j] = 1 + min(DP[(i - 1) % 2][j],

min(DP[i % 2][j - 1],

DP[(i - 1) % 2][j - 1]));

// after complete fill the DP array

// if the len2 is even then we end

// up in the 0th row else we end up

// in the 1th row so we take len2 % 2

// to get row

cout << DP[len2 % 2][len1] << endl;

// Driver program

int main()

string str1 = "food";

string str2 = "money";

EditDistDP(str1, str2);

return 0;
}

Output

Time Complexity: O(m x n)


Auxiliary Space: O( m )

This is a memoized version of recursion i.e. Top-Down DP:

C++Java

#include <bits/stdc++.h>

using namespace std;

int minDis(string s1, string s2, int n, int m,

vector<vector<int> >& dp)

// If any string is empty,

// return the remaining characters of other string

if (n == 0)

return m;

if (m == 0)

return n;

// To check if the recursive tree

// for given n & m has already been executed

if (dp[n][m] != -1)

return dp[n][m];

// If characters are equal, execute

// recursive function for n-1, m-1


if (s1[n - 1] == s2[m - 1]) {

return dp[n][m] = minDis(s1, s2, n - 1, m - 1, dp);

// If characters are nt equal, we need to

// find the minimum cost out of all 3 operations.

// 1. insert 2.delete 3.replace

else {

int insert, del, replace; // temp variables

insert = minDis(s1, s2, n, m - 1, dp);

del = minDis(s1, s2, n - 1, m, dp);

replace = minDis(s1, s2, n - 1, m - 1, dp);

return dp[n][m] = 1 + min(insert, min(del, replace));

// Driver program

int main()

string str1 = "voldemort";

string str2 = "dumbledore";

int n = str1.length(), m = str2.length();

vector<vector<int> > dp(n + 1, vector<int>(m + 1, -1));

cout << minDis(str1, str2, n, m, dp);

return 0;

// This code is a contribution of Bhavneet Singh

}
Output

Time Complexity: O(m x n)


Auxiliary Space: O( m *n)+O(m+n)

(m*n) extra array space and (m+n) recursive stack space.

Applications: There are many practical applications of edit distance algorithm, refer Lucene API for
sample. Another example, display all the words in a dictionary that are near proximity to a given
wordincorrectly spelled word.

Mark as Read

Report An IssueIf you are facing any i


Longest Increasing Subsequence

The Longest Increasing Subsequence (LIS) problem is to find the length of the longest subsequence of
a given sequence such that all elements of the subsequence are sorted in increasing order. For
example, the length of LIS for {10, 22, 9, 33, 21, 50, 41, 60, 80} is 6 and LIS is {10, 22, 33, 50, 60, 80}.

Examples:

Input: arr[] = {3, 10, 2, 1, 20}

Output: Length of LIS = 3

The longest increasing subsequence is 3, 10, 20

Input: arr[] = {3, 2}

Output: Length of LIS = 1

The longest increasing subsequences are {3} and {2}

Input: arr[] = {50, 3, 10, 7, 40, 80}

Output: Length of LIS = 4

The longest increasing subsequence is {3, 7, 40, 80}

Method 1: Recursion.
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.
Formally, the length of the longest increasing subsequence ending at index i, will be 1 greater than
the maximum of lengths of all longest increasing subsequences ending at indices before i, where
arr[j] < arr[i] (j < i).
Thus, we see the LIS problem satisfies the optimal substructure property as the main problem can be
solved using solutions to subproblems.

The recursive tree given below will make the approach clearer:

Input : arr[] = {3, 10, 2, 11}


f(i): Denotes LIS of subarray ending at index 'i'

(LIS(1)=1)

f(4) {f(4) = 1 + max(f(1), f(2), f(3))}

/ | \

f(1) f(2) f(3) {f(3) = 1, f(2) and f(1) are > f(3)}

| | \

f(1) f(2) f(1) {f(2) = 1 + max(f(1)}

f(1) {f(1) = 1}

Below is the implementation of the recursive approach:

C++Java

/* A Naive C++ recursive implementation

of LIS problem */

#include <iostream>

using namespace std;

/* To make use of recursive calls, this

function must return two things:

1) Length of LIS ending with element arr[n-1].

We use max_ending_here for this purpose

2) Overall maximum as the LIS may end with

an element before arr[n-1] max_ref is

used this purpose.

The value of LIS of full array of size n

is stored in *max_ref which is our final result

*/

int _lis(int arr[], int n, int* max_ref)

/* Base case */
if (n == 1)

return 1;

// 'max_ending_here' is length of LIS

// ending with arr[n-1]

int res, max_ending_here = 1;

/* Recursively get all LIS ending with arr[0],

arr[1] ... arr[n-2]. If arr[i-1] is smaller

than arr[n-1], and max ending with arr[n-1]

needs to be updated, then update it */

for (int i = 1; i < n; i++) {

res = _lis(arr, i, max_ref);

if (arr[i - 1] < arr[n - 1]

&& res + 1 > max_ending_here)

max_ending_here = res + 1;

// Compare max_ending_here with the overall

// max. And update the overall max if needed

if (*max_ref < max_ending_here)

*max_ref = max_ending_here;

// Return length of LIS ending with arr[n-1]

return max_ending_here;

// The wrapper function for _lis()

int lis(int arr[], int n)

// The max variable holds the result


int max = 1;

// The function _lis() stores its result in max

_lis(arr, n, &max);

// returns max

return max;

/* Driver program to test above function */

int main()

int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };

int n = sizeof(arr) / sizeof(arr[0]);

cout <<"Length of lis is "<< lis(arr, n);

return 0;

Output

Length of lis is 5

Complexity Analysis:

• Time Complexity: The time complexity of this recursive approach is exponential as there is a
case of overlapping subproblems as explained in the recursive tree diagram above.

• Auxiliary Space: O(1). No external space used for storing values apart from the internal stack
space.

Method 2: Dynamic Programming.


We can see that there are many subproblems in the above recursive solution which are solved again
and again. So this problem has Overlapping Substructure property and recomputation of same
subproblems can be avoided by either using Memoization or Tabulation.

The simulation of approach will make things clear:

Input : arr[] = {3, 10, 2, 11}

LIS[] = {1, 1, 1, 1} (initially)

Iteration-wise simulation :
1. arr[2] > arr[1] {LIS[2] = max(LIS [2], LIS[1]+1)=2}

2. arr[3] < arr[1] {No change}

3. arr[3] < arr[2] {No change}

4. arr[4] > arr[1] {LIS[4] = max(LIS [4], LIS[1]+1)=2}

5. arr[4] > arr[2] {LIS[4] = max(LIS [4], LIS[2]+1)=3}

6. arr[4] > arr[3] {LIS[4] = max(LIS [4], LIS[3]+1)=3}

We can avoid recomputation of subproblems by using tabulation as shown in the below code:

Below is the implementation of the above approach:

C++Java

/* Dynamic Programming C++ implementation

of LIS problem */

#include <bits/stdc++.h>

using namespace std;

/* 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);

/* Driver program to test above function */

int main()

int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };

int n = sizeof(arr) / sizeof(arr[0]);

printf("Length of lis is %d\n", lis(arr, n));

return 0;

Output

Length of lis is 5

Complexity Analysis:

• Time Complexity: O(n2).


As nested loop is used.

• Auxiliary Space: O(n).


Use of any array to store LIS values at each index.

Method 3 : Memoization DP

This is extension of recursive method

We can see that there are many subproblems in the above recursive solution which are solved again
and again. So this problem has Overlapping Substructure property and recomputation of same
subproblems can be avoided by either using Memoization

C++Java

/* A Naive C++ recursive implementation

of LIS problem */

#include <bits/stdc++.h>

#include <iostream>

using namespace std;


/* To make use of recursive calls, this

function must return two things:

1) Length of LIS ending with element arr[n-1].

We use max_ending_here for this purpose

2) Overall maximum as the LIS may end with

an element before arr[n-1] max_ref is

used this purpose.

The value of LIS of full array of size n

is stored in *max_ref which is our final result

*/

int f(int idx, int prev_idx, int n, int a[],

vector<vector<int> >& dp)

if (idx == n) {

return 0;

if (dp[idx][prev_idx + 1] != -1) {

return dp[idx][prev_idx + 1];

int notTake = 0 + f(idx + 1, prev_idx, n, a, dp);

int take = INT_MIN;

if (prev_idx == -1 || a[idx] > a[prev_idx]) {

take = 1 + f(idx + 1, idx, n, a, dp);

return dp[idx][prev_idx + 1] = max(take, notTake);

}
// Function to find length of longest increasing

// subsequence.

int longestSubsequence(int n, int a[])

vector<vector<int> > dp(n + 1, vector<int>(n + 1, -1));

return f(0, -1, n, a, dp);

/* Driver program to test above function */

int main()

int a[] = { 3, 10, 2, 1, 20 };

int n = sizeof(a) / sizeof(a[0]);

cout << "Length of lis is " << longestSubsequence(n, a);

return 0;

Output

Length of lis is 3

Complexity Analysis:

Time Complexity: O(n2).


Auxiliary Space: O(n2).

Mark as Read

Report An IssueIf you are facing any i


Longest Increasing Subsequence in O(nlogn)

Given an array of random numbers. Find longest increasing subsequence (LIS) in the array. I know
many of you might have read recursive and dynamic programming (DP) solutions. There are few
requests for O(N log N) algo in the forum posts.

For the time being, forget about recursive and DP solutions. Let us take small samples and extend the
solution to large instances. Even though it may look complex at first time, once if we understood the
logic, coding is simple.
Consider an input array A = {2, 5, 3}. I will extend the array during explanation.
By observation we know that the LIS is either {2, 3} or {2, 5}. Note that I am considering only strictly
increasing sequences.
Let us add two more elements, say 7, 11 to the array. These elements will extend the existing
sequences. Now the increasing sequences are {2, 3, 7, 11} and {2, 5, 7, 11} for the input array {2, 5, 3,
7, 11}.
Further, we add one more element, say 8 to the array i.e. input array becomes {2, 5, 3, 7, 11, 8}. Note
that the latest element 8 is greater than smallest element of any active sequence (will discuss shortly
about active sequences). How can we extend the existing sequences with 8? First of all, can 8 be part
of LIS? If yes, how? If we want to add 8, it should come after 7 (by replacing 11).
Since the approach is offline (what we mean by offline?), we are not sure whether adding 8 will
extend the series or not. Assume there is 9 in the input array, say {2, 5, 3, 7, 11, 8, 7, 9 ...}. We can
replace 11 with 8, as there is potentially best candidate (9) that can extend the new series {2, 3, 7, 8}
or {2, 5, 7, 8}.
Our observation is, assume that the end element of largest sequence is E. We can add (replace)
current element A[i] to the existing sequence if there is an element A[j] (j > i) such that E < A[i] < A[j]
or (E > A[i] < A[j] - for replace). In the above example, E = 11, A[i] = 8 and A[j] = 9.
In case of our original array {2, 5, 3}, note that we face same situation when we are adding 3 to
increasing sequence {2, 5}. I just created two increasing sequences to make explanation simple.
Instead of two sequences, 3 can replace 5 in the sequence {2, 5}.
I know it will be confusing, I will clear it shortly!
The question is, when will it be safe to add or replace an element in the existing sequence?
Let us consider another sample A = {2, 5, 3}. Say, the next element is 1. How can it extend the current
sequences {2, 3} or {2, 5}. Obviously, it can't extend either. Yet, there is a potential that the new
smallest element can be start of an LIS. To make it clear, consider the array is {2, 5, 3, 1, 2, 3, 4, 5, 6}.
Making 1 as new sequence will create new sequence which is largest.
The observation is, when we encounter new smallest element in the array, it can be a potential
candidate to start new sequence.
From the observations, we need to maintain lists of increasing sequences.
In general, we have set of active lists of varying length. We are adding an element A[i] to these lists.
We scan the lists (for end elements) in decreasing order of their length. We will verify the end
elements of all the lists to find a list whose end element is smaller than A[i] (floor value).
Our strategy determined by the following conditions,

1. If A[i] is smallest among all end

candidates of active lists, we will start


new active list of length 1.

2. If A[i] is largest among all end candidates of

active lists, we will clone the largest active

list, and extend it by A[i].

3. If A[i] is in between, we will find a list with

largest end element that is smaller than A[i].

Clone and extend this list by A[i]. We will discard all

other lists of same length as that of this modified list.

Note that at any instance during our construction of active lists, the following condition is
maintained.
"end element of smaller list is smaller than end elements of larger lists".
It will be clear with an example, let us take example from wiki {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3,
11, 7, 15}.

A[0] = 0. Case 1. There are no active lists, create one.

0.

-----------------------------------------------------------------------------

A[1] = 8. Case 2. Clone and extend.

0.

0, 8.

-----------------------------------------------------------------------------

A[2] = 4. Case 3. Clone, extend and discard.

0.

0, 4.

0, 8. Discarded

-----------------------------------------------------------------------------

A[3] = 12. Case 2. Clone and extend.

0.

0, 4.

0, 4, 12.

-----------------------------------------------------------------------------

A[4] = 2. Case 3. Clone, extend and discard.

0.
0, 2.

0, 4. Discarded.

0, 4, 12.

-----------------------------------------------------------------------------

A[5] = 10. Case 3. Clone, extend and discard.

0.

0, 2.

0, 2, 10.

0, 4, 12. Discarded.

-----------------------------------------------------------------------------

A[6] = 6. Case 3. Clone, extend and discard.

0.

0, 2.

0, 2, 6.

0, 2, 10. Discarded.

-----------------------------------------------------------------------------

A[7] = 14. Case 2. Clone and extend.

0.

0, 2.

0, 2, 6.

0, 2, 6, 14.

-----------------------------------------------------------------------------

A[8] = 1. Case 3. Clone, extend and discard.

0.

0, 1.

0, 2. Discarded.

0, 2, 6.

0, 2, 6, 14.

-----------------------------------------------------------------------------

A[9] = 9. Case 3. Clone, extend and discard.

0.
0, 1.

0, 2, 6.

0, 2, 6, 9.

0, 2, 6, 14. Discarded.

-----------------------------------------------------------------------------

A[10] = 5. Case 3. Clone, extend and discard.

0.

0, 1.

0, 1, 5.

0, 2, 6. Discarded.

0, 2, 6, 9.

-----------------------------------------------------------------------------

A[11] = 13. Case 2. Clone and extend.

0.

0, 1.

0, 1, 5.

0, 2, 6, 9.

0, 2, 6, 9, 13.

-----------------------------------------------------------------------------

A[12] = 3. Case 3. Clone, extend and discard.

0.

0, 1.

0, 1, 3.

0, 1, 5. Discarded.

0, 2, 6, 9.

0, 2, 6, 9, 13.

-----------------------------------------------------------------------------

A[13] = 11. Case 3. Clone, extend and discard.

0.

0, 1.

0, 1, 3.
0, 2, 6, 9.

0, 2, 6, 9, 11.

0, 2, 6, 9, 13. Discarded.

-----------------------------------------------------------------------------

A[14] = 7. Case 3. Clone, extend and discard.

0.

0, 1.

0, 1, 3.

0, 1, 3, 7.

0, 2, 6, 9. Discarded.

0, 2, 6, 9, 11.

----------------------------------------------------------------------------

A[15] = 15. Case 2. Clone and extend.

0.

0, 1.

0, 1, 3.

0, 1, 3, 7.

0, 2, 6, 9, 11.

0, 2, 6, 9, 11, 15. <-- LIS List

----------------------------------------------------------------------------

It is required to understand above strategy to devise an algorithm. Also, ensure we have maintained
the condition, "end element of smaller list is smaller than end elements of larger lists". Try with few
other examples, before reading further. It is important to understand what happening to end
elements.
Algorithm:
Querying length of longest is fairly easy. Note that we are dealing with end elements only. We need
not to maintain all the lists. We can store the end elements in an array. Discarding operation can be
simulated with replacement, and extending a list is analogous to adding more elements to array.
We will use an auxiliary array to keep end elements. The maximum length of this array is that of
input. In the worst case the array divided into N lists of size one (note that it doesn't lead to worst
case complexity). To discard an element, we will trace ceil value of A[i] in auxiliary array (again
observe the end elements in your rough work), and replace ceil value with A[i]. We extend a list by
adding element to auxiliary array. We also maintain a counter to keep track of auxiliary array length.
Bonus: You have learnt Patience Sorting technique partially :)
Here is a proverb, "Tell me and I will forget. Show me and I will remember. Involve me and I will
understand." So, pick a suit from deck of cards. Find the longest increasing sub-sequence of cards
from the shuffled suit. You will never forget the approach. :)
Update - 17 July, 2016: Quite impressive responses from the readers and few sites referring the post,
feeling happy as my hardwork helping others. It looks like readers are not doing any homework prior
to posting comments. Requesting to run through some examples after reading the article, and please
do your work on paper (don't use editor/compiler). The request is to help yourself. Profess to 'know'
is different from real understanding (no disrespect). Given below was my personal experience.
Initial content preparation took roughly 6 hours to me. But, it was a good lesson. I finished initial code
in an hour. When I start writing content to explain the reader, I realized I didn't understand the cases.
Took my note book (I have habit of maintaining binded note book to keep track of my rough work),
and after few hours I filled nearly 15 pages of rough work. Whatever the content you are seeing in
the gray colored example is from these pages. All the thought process for the solution triggered by a
note in the book 'Introduction to Algorithms by Udi Manber', I strongly recommend to practice the
book.
I suspect, many readers might not get the logic behind CeilIndex (binary search). I leave it as an
exercise to the reader to understand how it works. Run through few examples on paper. I realized I
have already covered the algorithm in another post.
Update - 5th August, 2016:
The following link worth referring after you do your work. I got to know the link via my recently
created Disqus profile. The link has explanation of approach mentioned in the Wiki.
http://stackoverflow.com/questions/2631726/how-to-determine-the-longest-increasing-
subsequence-using-dynamic-programming
Given below is code to find length of LIS (updated to C++11 code, no C-style arrays),

C++Java

#include <iostream>

#include <vector>

// Binary search (note boundaries in the caller)

int CeilIndex(std::vector<int>& v, int l, int r, int key)

while (r - l > 1) {

int m = l + (r - l) / 2;

if (v[m] >= key)

r = m;

else

l = m;

return r;
}

int LongestIncreasingSubsequenceLength(std::vector<int>& v)

if (v.size() == 0)

return 0;

std::vector<int> tail(v.size(), 0);

int length = 1; // always points empty slot in tail

tail[0] = v[0];

for (size_t i = 1; i < v.size(); i++) {

// new smallest value

if (v[i] < tail[0])

tail[0] = v[i];

// v[i] extends largest subsequence

else if (v[i] > tail[length - 1])

tail[length++] = v[i];

// v[i] will become end candidate of an existing

// subsequence or Throw away larger elements in all

// LIS, to make room for upcoming greater elements

// than v[i] (and also, v[i] would have already

// appeared in one of LIS, identify the location

// and replace it)

else

tail[CeilIndex(tail, -1, length - 1, v[i])] = v[i];

}
return length;

int main()

std::vector<int> v{ 2, 5, 3, 7, 11, 8, 10, 13, 6 };

std::cout << "Length of Longest Increasing Subsequence is "

<< LongestIncreasingSubsequenceLength(v) << '\n';

return 0;

Output:

Length of Longest Increasing Subsequence is 6

Complexity:
The loop runs for N elements. In the worst case (what is worst case input?), we may end up querying
ceil value using binary search (log i) for many A[i].
Therefore, T(n) < O( log N! ) = O(N log N). Analyse to ensure that the upper and lower bounds are
also O( N log N ). The complexity is THETA (N log N).
Exercises:
1. Design an algorithm to construct the longest increasing list. Also, model your solution using DAGs.
2. Design an algorithm to construct all increasing lists of equal longest size.
3. Is the above algorithm an online algorithm?
4. Design an algorithm to construct the longest decreasing list..
Alternate implementation in various languages using their built in binary search functions are
given below:

CPPJava

#include <bits/stdc++.h>

using namespace std;

int LongestIncreasingSubsequenceLength(std::vector<int>& v)

if (v.size() == 0) // boundary case

return 0;

std::vector<int> tail(v.size(), 0);

int length = 1; // always points empty slot in tail


tail[0] = v[0];

for (int i = 1; i < v.size(); i++) {

// Do binary search for the element in

// the range from begin to begin + length

auto b = tail.begin(), e = tail.begin() + length;

auto it = lower_bound(b, e, v[i]);

// If not present change the tail element to v[i]

if (it == tail.begin() + length)

tail[length++] = v[i];

else

*it = v[i];

return length;

int main()

std::vector<int> v{ 2, 5, 3, 7, 11, 8, 10, 13, 6 };

std::cout

<< "Length of Longest Increasing Subsequence is "

<< LongestIncreasingSubsequenceLength(v);

return 0;

Output:

Length of Longest Increasing Subsequence is 6

Time Complexity: O(nlogn)


Space Complexity: O(n)

Mark as Read

Report An IssueIf you are facing any i


Maximum Cuts

Given a rod of length L, the task is to cut the rod in such a way that the total number of segments of
length p, q and r is maximized. The segments can only be of length p, q, and r.

Examples:

Input: l = 11, p = 2, q = 3, r = 5
Output: 5
Segments of 2, 2, 2, 2 and 3

Input: l = 7, p = 2, q = 5, r = 5
Output: 2
Segments of 2 and 5

Approach 1:

This can be visualized as a classical recursion problem , which further narrows down
to memoization ( top-down ) method of Dynamic Programming. Initially , we have length l present
with us , we'd have three size choices to cut from this , either we can make a cut of length p , or q ,
or r. Let's say we made a cut of length p , so the remaining length would be l-p and similarly with
cuts q & r resulting in remaining lengths l-q & l-r respectively. We will call recursive function for the
remaining lengths and at any subsequent instance we'll have these three choices. We will store the
answer from all these recursive calls & take the maximum out of them +1 as at any instance we'll
have 1 cut from this particular call as well. Also , note that the recursive call would be made if and
only if the available length is greater than length we want to cut i.e. suppose p=3 , and after certain
recursive calls the available length is 2 only , so we can't cut this line in lengths of p anymore.

Below is the pseudocode for the same:

if(l==0) // Base Case

return 0;

int a,b,c;

if(p<=l)

a=func(l-p,p,q,r);

if(q<=l)

b=func(l-q,p,q,r);

if(r<=l)

c=func(l-r,p,q,r);

return 1+max({a,b,c});

Below is the recursion tree for l=4,p=2,q=1 and r=1:


Recursion Tree for l=4 , p=2 ,q=1 and r=1

One can clearly observe that at each call , the given length ( 4 initially ) is divided into 3 different
subparts. Also , we can see that the recursion is being repeated for certain entries ( Red arrow
represents repetitive call for l=2, Yellow for l=3 and Blue for l=1). Therefore , we can memoize the
results in any container or array , so that repetition of same recursive calls is avoided.

Now , the above pseudocode changes to :

vector<int> dp(10005,-1); // Initialise DP Table ( Array can also be used )

if(l==0) // Base Case

return 0;

if(dp[l]!=-1) // If already memoized , return from here only

return dp[l];

int a,b,c;

if(p<=l)

a=func(l-p,p,q,r);

if(q<=l)

b=func(l-q,p,q,r);

if(r<=l)

c=func(l-r,p,q,r);

return dp[l]=1+max({a,b,c}); // Memoize the result in the dp table & return

Let's now follow the code for implementation of the above code :
C++

//C++ Code to find maximum number of cut segments

// Memoization DP

#include <bits/stdc++.h>

using namespace std;

//Function to find the maximum number of cuts.

int dp[10005];

int func(int l, int p, int q, int r)

if(l==0)

return 0; // Base Case

if(dp[l]!=-1) // If already memoized

return dp[l];

int a(INT_MIN),b(INT_MIN),c(INT_MIN); // Intitialise a,b,& c with INT_MIN

if(p<=l) // If Possible to make a cut of length p

a=func(l-p,p,q,r);

if(q<=l) // If possible to make a cut of length q

b=func(l-q,p,q,r);

if(r<=l) // If possible to make a cut of length r

c=func(l-r,p,q,r);

return dp[l]=1+max({a,b,c}); // Memoize & return


}

int maximizeTheCuts(int l, int p, int q, int r)

memset(dp,-1,sizeof(dp)); // Set Lookup table to -1

int ans=func(l,p,q,r); // Utility function call

if(ans<0)

return 0; // If returned answer is negative , that means cuts are not possible

return ans;

int main()

int l,p,q,r;

cout<<"ENTER THE LENGTH OF THE ROD "<<endl;

cin>>l;

cout<<"ENTER THE VALUES OF p,q & r "<<endl;

cin>>p>>q>>r;

cout<<"THE MAXIMUM NUMBER OF SEGMENTS THAT CAN BE CUT OF LENGTH p,q & r FROM A ROD
OF LENGTH l are "<<maximizeTheCuts(l,p,q,r)<<endl;

return 0;

Time Complexity : O(n) where n is the length of rod or line segment that has to be cut.

Space Complexity : O(n) where n is the length of rod or line segment that has to be cut.

Approach 2:

As the solution for a maximum number of cuts that can be made in a given length depends on the
maximum number of cuts previously made in shorter lengths, this question could be solved by the
approach of Dynamic Programming. Suppose we are given a length 'l'. For finding the maximum
number of cuts that can be made in length 'l', find the number of cuts made in shorter
previous length 'l-p', 'l-q', 'l-r' lengths respectively. The required answer would be the max(l-p,l-q,l-
r)+1 as one more cut should be needed after this to cut length 'l'. So for solving this problem for a
given length, find the maximum number of cuts that can be made in lengths ranging from '1' to 'l'.

Example:

l = 11, p = 2, q = 3, r = 5
Analysing lengths from 1 to 11:

1. Not possible to cut->0

2. Possible cut is of lengths 2->1 (2)

3. Possible cut is of lengths 3->1 (3)

4. Possible cuts are of lengths max(arr[4-2],arr[4-3])+1->2 (2,2)

5. Possible cuts are of lengths max(arr[5-2],arr[5-3])+1->2 (2,3)

6. Possible cuts are of lengths max(arr[6-2],arr[6-3],arr[6-5])+1->3 (2,2,2)

7. Possible cuts are of lengths max(arr[7-2],arr[7-3],arr[7-5])+1->3 (2,3,2) or (2,2,3)

8. Possible cuts are of lengths max(arr[8-2],arr[8-3],arr[8-5])+1->4 (2,2,2,2)

9. Possible cuts are of lengths max(arr[9-2],arr[9-3],arr[9-5])+1->4 (2,3,2,2) or (2,2,3,2) or


(2,2,2,3)

10. Possible cuts are of lengths max(arr[10-2],arr[10-3],arr[10-5])+1->5 (2,2,2,2,2)

11. Possible cuts are of lengths max(arr[11-2],arr[11-3],arr[11-5])+1->5 (2,3,2,2,2) or (2,2,3,2,2)


or (2,2,2,3,2) or (2,2,2,2,3)

Algorithm:

1. Initialise an array DP[]={-1} and DP[0]=0.

2. Run a loop from '1' to 'l'

3. If DP[i]=-1 means it's not possible to divide it using giving segments p,q,r so continue;

4. DP[i+p]=max(DP[i+p],DP[i]+1)

5. DP[i+q]=max(DP[i+q],DP[i]+1)

6. DP[i+r]=max(DP[i+r],DP[i]+1)

7. print DP[l]

Pseudo Code:

DP[l+1]={-1}

DP[0]=0

for(i from 0 to l)
if(DP[i]==-1)

continue

DP[i+p]=max(DP[i+p],DP[i]+1)

DP[i+q]=max(DP[i+q],DP[i]+1)

DP[i+r]=max(DP[i+r],DP[i]+1)

print(DP[l])

Implementation:

C++Java

// C++ program to maximize the number

// of segments of length p, q and r

#include <bits/stdc++.h>

using namespace std;

// Function that returns the maximum number

// of segments possible

int findMaximum(int l, int p, int q, int r)

// Array to store the cut at each length

int dp[l + 1];

// All values with -1

memset(dp, -1, sizeof(dp));

// if length of rod is 0 then total cuts will be 0

// so, initialize the dp[0] with 0

dp[0] = 0;

for (int i = 0; i <= l; i++) {


// if certain length is not possible

if (dp[i] == -1)

continue;

// if a segment of p is possible

if (i + p <= l)

dp[i + p] = max(dp[i + p], dp[i] + 1);

// if a segment of q is possible

if (i + q <= l)

dp[i + q] = max(dp[i + q], dp[i] + 1);

// if a segment of r is possible

if (i + r <= l)

dp[i + r] = max(dp[i + r], dp[i] + 1);

// if no segment can be cut then return 0

if (dp[l] == -1) {

dp[l] = 0;

// return value corresponding to length l

return dp[l];

// Driver Code

int main()

int l = 11, p = 2, q = 3, r = 5;

// Calling Function

int ans = findMaximum(l, p, q, r);


cout << ans;

return 0;

Output

Complexity Analysis:

• Time Complexity: O(N).


Use of a single for-loop till length 'N'.

• Auxiliary Space: O(N).


Use of an array 'DP' to keep track of segments

Note: This problem can also be thought of as a minimum coin change problem because we are given
a certain length to acquire which is the same as the value of the amount whose minimum change is
needed. Now the x,y,z are the same as the denomination of the coin given. So length is the same as
the amount and x y z are the same as denominations, thus we need to change only one condition
that is instead of finding minimum we need to find the maximum and we will get the answer. As the
minimum coin change problem is the basic dynamic programming question so this will help to solve
this question also.

The condition we need to change in minimum coin change problem

for(ll i=1;i<=n;i++)

for(ll j=1;j<=3;j++)

if(i>=a[j]&&m[i-a[j]]!=-1)

dp[i]=max(dp[i],1+dp[i-a[j]]);

Mark as Read

Report An IssueIf you are facing any i


Minimum coins to make a value

Given a value V, if we want to make a change for V cents, and we have an infinite supply of each of C
= { C1, C2, .., Cm} valued coins, what is the minimum number of coins to make the change? If it's not
possible to make a change, print -1.

Examples:

Input: coins[] = {25, 10, 5}, V = 30


Output: Minimum 2 coins required We can use one coin of 25 cents and one of 5 cents

Input: coins[] = {9, 6, 5, 1}, V = 11


Output: Minimum 2 coins required We can use one coin of 6 cents and 1 coin of 5 cents

This problem is a variation of the problem discussed Coin Change Problem. Here instead of finding
the total number of possible solutions, we need to find the solution with the minimum number of
coins.

The minimum number of coins for a value V can be computed using the below recursive formula.

If V == 0, then 0 coins required.


If V > 0
minCoins(coins[0..m-1], V) = min {1 + minCoins(V-coin[i])}
where i varies from 0 to m-1
and coin[i] <= V

Below is a recursive solution based on the above recursive formula.

C++Java

// A Naive recursive C++ program to find minimum of coins

// to make a given change V

#include<bits/stdc++.h>

using namespace std;

// m is size of coins array (number of different coins)

int minCoins(int coins[], int m, int V)

// base case

if (V == 0) return 0;

// Initialize result
int res = INT_MAX;

// Try every coin that has smaller value than V

for (int i=0; i<m; i++)

if (coins[i] <= V)

int sub_res = minCoins(coins, m, V-coins[i]);

// Check for INT_MAX to avoid overflow and see if

// result can minimized

if (sub_res != INT_MAX && sub_res + 1 < res)

res = sub_res + 1;

return res;

// Driver program to test above function

int main()

int coins[] = {9, 6, 5, 1};

int m = sizeof(coins)/sizeof(coins[0]);

int V = 11;

cout << "Minimum coins required is "

<< minCoins(coins, m, V);

return 0;

Output
Minimum coins required is 2

The time complexity of the above solution is exponential and space complexity is way greater than
O(n). If we draw the complete recursion tree, we can observe that many subproblems are solved
again and again. For example, when we start from V = 11, we can reach 6 by subtracting one 5 times
and by subtracting 5 one time. So the subproblem for 6 is called twice.

Since the same subproblems are called again, this problem has the Overlapping Subproblems
property. So the min coins problem has both properties (see this and this) of a dynamic programming
problem. Like other typical Dynamic Programming(DP) problems, recomputations of the same
subproblems can be avoided by constructing a temporary array table[][] in a bottom-up manner.
Below is Dynamic Programming based solution.

C++Java

// A Dynamic Programming based C++ program to find minimum of coins

// to make a given change V

#include<bits/stdc++.h>

using namespace std;

// m is size of coins array (number of different coins)

int minCoins(int coins[], int m, int V)

// table[i] will be storing the minimum number of coins

// required for i value. So table[V] will have result

int table[V+1];

// Base case (If given value V is 0)

table[0] = 0;

// Initialize all table values as Infinite

for (int i=1; i<=V; i++)

table[i] = INT_MAX;

// Compute minimum coins required for all

// values from 1 to V

for (int i=1; i<=V; i++)


{

// Go through all coins smaller than i

for (int j=0; j<m; j++)

if (coins[j] <= i)

int sub_res = table[i-coins[j]];

if (sub_res != INT_MAX && sub_res + 1 < table[i])

table[i] = sub_res + 1;

if(table[V]==INT_MAX)

return -1;

return table[V];

// Driver program to test above function

int main()

int coins[] = {9, 6, 5, 1};

int m = sizeof(coins)/sizeof(coins[0]);

int V = 11;

cout << "Minimum coins required is "

<< minCoins(coins, m, V);

return 0;

Output

Minimum coins required is 2

Time complexity: O(mV).


Minimum Jumps to reach at end

Given an array arr[] where each element represents the max number of steps that can be made
forward from that index. The task is to find the minimum number of jumps to reach the end of the
array starting from index 0. If the end isn’t reachable, return -1.

Examples:

Input: arr[] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9}


Output: 3 (1-> 3 -> 9 -> 9)
Explanation: Jump from 1st element to 2nd element as there is only 1 step.
Now there are three options 5, 8 or 9. I
f 8 or 9 is chosen then the end node 9 can be reached. So 3 jumps are made.

Input: arr[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}


Output: 10
Explanation: In every step a jump is needed so the count of jumps is 10.

Minimum number of jumps to reach end using Dynamic Programming from left to right:

It can be observed that there will be overlapping subproblems.

For example in array, arr[] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9} minJumps(3, 9) will be called two times as
arr[3] is reachable from arr[1] and arr[2]. So this problem has both properties (optimal
substructure and overlapping subproblems) of Dynamic Programming

Follow the below steps to implement the idea:

• Create jumps[] array from left to right such that jumps[i] indicate the minimum number of
jumps needed to reach arr[i] from arr[0].

• To fill the jumps array run a nested loop inner loop counter is j and the outer loop count is i.

o Outer loop from 1 to n-1 and inner loop from 0 to i.

o If i is less than j + arr[j] then set jumps[i] to minimum of jumps[i] and jumps[j] + 1.
initially set jump[i] to INT MAX

• Return jumps[n-1].

Below is the implementation of the above approach:

C++Java

// C++ program for Minimum number

// of jumps to reach end

#include <bits/stdc++.h>

using namespace std;


int min(int x, int y) { return (x < y) ? x : y; }

// Returns minimum number of jumps

// to reach arr[n-1] from arr[0]

int minJumps(int arr[], int n)

// jumps[n-1] will hold the result

int* jumps = new int[n];

int i, j;

if (n == 0 || arr[0] == 0)

return INT_MAX;

jumps[0] = 0;

// Find the minimum number of jumps to reach arr[i]

// from arr[0], and assign this value to jumps[i]

for (i = 1; i < n; i++) {

jumps[i] = INT_MAX;

for (j = 0; j < i; j++) {

if (i <= j + arr[j] && jumps[j] != INT_MAX) {

jumps[i] = min(jumps[i], jumps[j] + 1);

break;

return jumps[n - 1];

// Driver code
int main()

int arr[] = { 1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9 };

int size = sizeof(arr) / sizeof(int);

cout << "Minimum number of jumps to reach end is "

<< minJumps(arr, size);

return 0;

Output

Minimum number of jumps to reach end is 3

Thanks to paras for suggesting this method.

Time Complexity: O(n2)


Auxiliary Space: O(n), since n extra space has been taken.

Another implementation using Dynamic programming:

Build jumps[] array from right to left such that jumps[i] indicate the minimum number of jumps
needed to reach arr[n-1] from arr[i]. Finally, we return jumps[0]. Use Dynamic programming in a
similar way of the above method.

Below is the Implementation of the above approach:

C++Java

// C++ program to find Minimum number of jumps to reach end

#include <bits/stdc++.h>

using namespace std;

// Returns Minimum number of jumps to reach end

int minJumps(int arr[], int n)

// jumps[0] will hold the result

int* jumps = new int[n];

int min;

// Minimum number of jumps needed to reach last element

// from last elements itself is always 0


jumps[n - 1] = 0;

// Start from the second element, move from right to

// left and construct the jumps[] array where jumps[i]

// represents minimum number of jumps needed to reach

// arr[m-1] from arr[i]

for (int i = n - 2; i >= 0; i--) {

// If arr[i] is 0 then arr[n-1] can't be reached

// from here

if (arr[i] == 0)

jumps[i] = INT_MAX;

// If we can directly reach to the end point from

// here then jumps[i] is 1

else if (arr[i] >= n - i - 1)

jumps[i] = 1;

// Otherwise, to find out the minimum number of

// jumps needed to reach arr[n-1], check all the

// points reachable from here and jumps[] value for

// those points

else {

// initialize min value

min = INT_MAX;

// following loop checks with all reachable

// points and takes the minimum

for (int j = i + 1; j < n && j <= arr[i] + i;

j++) {

if (min > jumps[j])

min = jumps[j];
}

// Handle overflow

if (min != INT_MAX)

jumps[i] = min + 1;

else

jumps[i] = min; // or INT_MAX

return jumps[0];

// Driver program to test above function

int main()

int arr[] = { 1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9 };

int size = sizeof(arr) / sizeof(int);

cout << "Minimum number of jumps to reach"

<< " end is " << minJumps(arr, size);

return 0;

Output

Minimum number of jumps to reach end is 3

Time complexity: O(n2). Nested traversal of the array is needed.


Auxiliary Space: O(n). To store the DP array linear space is needed.

Mark as Read

Report An IssueIf you are facing any i


0-1 knapsack problem

Given weights and values of n items, put these items in a knapsack of capacity W to get the
maximum total value in the knapsack. In other words, given two integer arrays val[0..n-1] and
wt[0..n-1] which represent values and weights associated with n items respectively. Also given an
integer W which represents knapsack capacity, find out the maximum value subset of val[] such that
sum of the weights of this subset is smaller than or equal to W. You cannot break an item, either pick
the complete item or don't pick it (0-1 property).

Method 1: Recursion by Brute-Force algorithm OR Exhaustive Search.


Approach: A simple solution is to consider all subsets of items and calculate the total weight and
value of all subsets. Consider the only subsets whose total weight is smaller than W. From all such
subsets, pick the maximum value subset.
Optimal Sub-structure: To consider all subsets of items, there can be two cases for every item.

1. Case 1: The item is included in the optimal subset.

2. Case 2: The item is 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 'nth' item is greater than 'W', then the nth item cannot be included and Case 1 is the
only possibility.

Below is the implementation of the above approach:

C++Java

/* A Naive recursive implementation of

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; }

// Returns the maximum value that

// can be put in a knapsack of capacity W

int knapSack(int W, int wt[], int val[], int n)

// Base Case

if (n == 0 || W == 0)

return 0;

// If weight of the nth item is more

// than Knapsack capacity W, then

// this item cannot be included

// in the optimal solution

if (wt[n - 1] > W)

return knapSack(W, wt, val, n - 1);

// Return the maximum of two cases:

// (1) nth item included

// (2) not included

else

return max(

val[n - 1]

+ knapSack(W - wt[n - 1],

wt, val, n - 1),


knapSack(W, wt, val, n - 1));

// Driver code

int main()

int val[] = { 60, 100, 120 };

int wt[] = { 10, 20, 30 };

int W = 50;

int n = sizeof(val) / sizeof(val[0]);

cout << knapSack(W, wt, val, n);

return 0;

// This code is contributed by rathbhupendra

Output

220

It should be noted that the above function computes the same sub-problems again and again. See
the following recursion tree, K(1, 1) is being evaluated twice. The time complexity of this naive
recursive solution is exponential (2^n).

In the following recursion tree, K() refers

to knapSack(). The two parameters indicated in the

following recursion tree are n and W.

The recursion tree is for following sample inputs.

wt[] = {1, 1, 1}, W = 2, val[] = {10, 20, 30}

K(n, W)

K(3, 2)

/ \

/ \

K(2, 2) K(2, 1)
/ \ / \

/ \ / \

K(1, 2) K(1, 1) K(1, 1) K(1, 0)

/ \ / \ / \

/ \ / \ / \

K(0, 2) K(0, 1) K(0, 1) K(0, 0) K(0, 1) K(0, 0)

Recursion tree for Knapsack capacity 2

units and 3 items of 1 unit weight.

Complexity Analysis:

• Time Complexity: O(2n).


As there are redundant subproblems.

• Auxiliary Space :O(1) + O(N).


As no extra data structure has been used for storing values but O(N) auxiliary stack
space(ASS) has been used for recursion stack.

Since subproblems are evaluated again, this problem has Overlapping Sub-problems property. So the
0-1 Knapsack problem has both properties (see this and this) of a dynamic programming problem.

Method 2: Like other typical Dynamic Programming(DP) problems, re-computation of same


subproblems can be avoided by constructing a temporary array K[][] in bottom-up manner. Following
is Dynamic Programming based implementation.

Approach: In the Dynamic programming we will work considering the same cases as mentioned in
the recursive approach. In a DP[][] table let's consider all the possible weights from '1' to 'W' as the
columns and weights that can be kept as the rows.
The state DP[i][j] will denote maximum value of 'j-weight' considering all values from '1 to ith'. So if
we consider 'wi' (weight in 'ith' row) we can fill it in all columns which have 'weight values > wi'. Now
two possibilities can take place:

• Fill 'wi' in the given column.

• Do not fill 'wi' in the given column.

Now we have to take a maximum of these two possibilities, formally if we do not fill 'ith' weight in
'jth' column then DP[i][j] state will be same as DP[i-1][j] but if we fill the weight, DP[i][j] will be equal
to the value of 'wi'+ value of the column weighing 'j-wi' in the previous row. So we take the
maximum of these two possibilities to fill the current state. This visualisation will make the concept
clear:

Let weight elements = {1, 2, 3}

Let weight values = {10, 15, 40}

Capacity=6
0 1 2 3 4 5 6

0 0 0 0 0 0 0 0

1 0 10 10 10 10 10 10

2 0 10 15 25 25 25 25

3 0

Explanation:

For filling 'weight = 2' we come

across 'j = 3' in which

we take maximum of

(10, 15 + DP[1][3-2]) = 25

| |

'2' '2 filled'

not filled

0 1 2 3 4 5 6

0 0 0 0 0 0 0 0

1 0 10 10 10 10 10 10

2 0 10 15 25 25 25 25

3 0 10 15 40 50 55 65

Explanation:

For filling 'weight=3',


we come across 'j=4' in which

we take maximum of (25, 40 + DP[2][4-3])

= 50

For filling 'weight=3'

we come across 'j=5' in which

we take maximum of (25, 40 + DP[2][5-3])

= 55

For filling 'weight=3'

we come across 'j=6' in which

we take maximum of (25, 40 + DP[2][6-3])

= 65

C++Java

// 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;

// 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;
vector<vector<int>> K(n + 1, vector<int>(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];

// Driver Code

int main()

int val[] = { 60, 100, 120 };

int wt[] = { 10, 20, 30 };

int W = 50;

int n = sizeof(val) / sizeof(val[0]);

cout << knapSack(W, wt, val, n);

return 0;
}

// This code is contributed by Debojyoti Mandal

Output

220

Complexity Analysis:

• Time Complexity: O(N*W).


where 'N' is the number of weight element and 'W' is capacity. As for every weight element
we traverse through all weight capacities 1<=w<=W.

• Auxiliary Space: O(N*W).


The use of 2-D array of size 'N*W'.

Scope for Improvement :- We used the same approach but with optimized space complexity

C++Java

#include <bits/stdc++.h>

using namespace std;

// we can further improve the above Knapsack function's space

// complexity

int knapSack(int W, int wt[], int val[], int n)

int i, w;

int K[2][W + 1];

// We know we are always using the current row or

// the previous row of the array/vector . Thereby we can

// improve it further by using a 2D array but with only

// 2 rows i%2 will be giving the index inside the bounds

// of 2d array K

for (i = 0; i <= n; i++) {

for (w = 0; w <= W; w++) {

if (i == 0 || w == 0)
K[i % 2][w] = 0;

else if (wt[i - 1] <= w)

K[i % 2][w] = max(

val[i - 1]

+ K[(i - 1) % 2][w - wt[i - 1]],

K[(i - 1) % 2][w]);

else

K[i % 2][w] = K[(i - 1) % 2][w];

return K[n % 2][W];

// Driver Code

int main()

int val[] = { 60, 100, 120 };

int wt[] = { 10, 20, 30 };

int W = 50;

int n = sizeof(val) / sizeof(val[0]);

cout << knapSack(W, wt, val, n);

return 0;

// This code was improved by Udit Singla

Complexity Analysis:

• Time Complexity: O(N*W).

• Auxiliary Space: O(2*W)


As we are using a 2-D array but with only 2 rows.
Method 3: This method uses Memoization Technique (an extension of recursive approach).
This method is basically an extension to the recursive approach so that we can overcome the
problem of calculating redundant cases and thus increased complexity. We can solve this problem by
simply creating a 2-D array that can store a particular state (n, w) if we get it the first time. Now if we
come across the same state (n, w) again instead of calculating it in exponential complexity we can
directly return its result stored in the table in constant time. This method gives an edge over the
recursive approach in this aspect.

C++Java

// Here is the top-down approach of

// dynamic programming

#include <bits/stdc++.h>

using namespace std;

// Returns the value of maximum profit

int knapSackRec(int W, int wt[],

int val[], int i,

int** dp)

// base condition

if (i < 0)

return 0;

if (dp[i][W] != -1)

return dp[i][W];

if (wt[i] > W) {

// Store the value of function call

// stack in table before return

dp[i][W] = knapSackRec(W, wt,

val, i - 1,

dp);

return dp[i][W];

}
else {

// Store value in a table before return

dp[i][W] = max(val[i]

+ knapSackRec(W - wt[i],

wt, val,

i - 1, dp),

knapSackRec(W, wt, val,

i - 1, dp));

// Return value of table after storing

return dp[i][W];

int knapSack(int W, int wt[], int val[], int n)

// double pointer to declare the

// table dynamically

int** dp;

dp = new int*[n];

// loop to create the table dynamically

for (int i = 0; i < n; i++)

dp[i] = new int[W + 1];

// loop to initially filled the

// table with -1

for (int i = 0; i < n; i++)

for (int j = 0; j < W + 1; j++)

dp[i][j] = -1;

return knapSackRec(W, wt, val, n - 1, dp);


}

// Driver Code

int main()

int val[] = { 60, 100, 120 };

int wt[] = { 10, 20, 30 };

int W = 50;

int n = sizeof(val) / sizeof(val[0]);

cout << knapSack(W, wt, val, n);

return 0;

Output

220

Complexity Analysis:

• Time Complexity: O(N*W).


As redundant calculations of states are avoided.

• Auxiliary Space: O(N*W) + O(N).


The use of 2D array data structure for storing intermediate states and O(N) auxiliary stack
space(ASS) has been used for recursion stack:

Mark as Read

Report An IssueIf you are facing any i

You might also like