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

Introduction To Dynamic Programming

Uploaded by

[L]Jinesh pande
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

Introduction To Dynamic Programming

Uploaded by

[L]Jinesh pande
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 128

Introduction to Dynamic Programming

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:

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:

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.
Properties of a Dynamic Programming Problem

There are two main properties of any problem which identifies a problem that it can be solved
using the dynamic programming approach:

1. Overlapping Subproblem Property


2. Optimal Substructure Property

Let us look at each one of these properties in details:

1. Overlapping Subproblems: Like Divide and Conquer, Dynamic Programming


combines solutions to sub-problems. Dynamic Programming is mainly used when
solutions of the same subproblems are needed again and again. In dynamic programming,
computed solutions to subproblems are stored in a table so that these don’t have to be
recomputed. So Dynamic Programming is not useful when there are no common
(overlapping) subproblems because there is no point storing the solutions if they are not
needed again. For example, Binary Search doesn’t have common subproblems. If we take
an example of following the recursive program for Fibonacci Numbers, there are many
subproblems which are solved again and again.


/* 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 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.
• Optimal Substructure: A given problem has Optimal Substructure Property if an optimal
solution of the given problem can be obtained by using optimal solutions of its subproblems. For
example, the Shortest Path problem has the following optimal substructure property: If a node x
lies in the shortest path from a source node u to destination node v then the shortest path from u
to v is combination of shortest path from u to x and shortest path from x to v. The standard All
Pair Shortest Path algorithms like Floyd–Warshall and Bellman-Ford are typical examples of
Dynamic Programming. On the other hand, the Longest Path problem doesn’t have the Optimal
Substructure property. Here, by Longest Path we mean longest simple path (path without cycle)
between any two nodes. Consider the following unweighted graph given in the CLRS book.
There are two longest paths from q to t: q->r->t and q->s->t. Unlike shortest paths, these longest
paths do not have the optimal substructure property. For example, the longest path q->r->t is not
a combination of the longest path from q to r and longest path from r to t, because the longest

path from q to r is q->s->t->r and the longest path from r to t is r->q->s->t.


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++ 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++ 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.
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.

// 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
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:
// 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
// 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.
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.
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]
}
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
n
C2 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.

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

// This code is contributed by rathbhupendra

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.

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


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
n
C2 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.

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

// This code is contributed by rathbhupendra

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.

/* 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.

/* 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;
}
// code submitted by Aditya Yadav (adityayadav012552)

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

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

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++ 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.

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:

#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
4
Time Complexity: O(M*sum)

Auxiliary Space: O(M*sum)


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.

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

Output
3

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.

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

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

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

Time Complexity: O(m x n)


Auxiliary Space: O( m )
This is a memoized version of recursion i.e. Top-Down DP:

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

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

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

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

/* 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).
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),

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

#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)


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++ 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++ 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
5

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]]);
}
}
}
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.

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

// 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++ 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++ 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.
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:

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

#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.

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


Optimal Strategy for a Game

Consider a row of n coins of values v1 . . . vn, where n is even. We play a game against an
opponent by alternating turns. In each turn, a player selects either the first or last coin from the
row, removes it from the row permanently, and receives the value of the coin. Determine the
maximum possible amount of money we can definitely win if we move first.
Note: The opponent is as clever as the user.

Let us understand the problem with few examples:

1. 5, 3, 7, 10 : The user collects maximum value as 15(10 + 5)


2. 8, 15, 3, 7 : The user collects maximum value as 22(7 + 15)

Does choosing the best at each move gives an optimal solution? No.
In the second example, this is how the game can be finished:

1. .......User chooses 8.
.......Opponent chooses 15.
.......User chooses 7.
.......Opponent chooses 3.
Total value collected by user is 15(8 + 7)
2. .......User chooses 7.
.......Opponent chooses 8.
.......User chooses 15.
.......Opponent chooses 3.
Total value collected by user is 22(7 + 15)

So if the user follows the second game state, the maximum value can be collected although the
first move is not the best.

Approach: As both the players are equally strong, both will try to reduce the possibility of
winning of each other. Now let's see how the opponent can achieve this.

There are two choices:

• The user chooses the 'ith' coin with value 'Vi': The opponent either chooses (i+1)th coin
or jth coin. The opponent intends to choose the coin which leaves the user with
minimum value.
i.e. The user can collect the value Vi + min(F(i+2, j), F(i+1, j-1) ) where [i+2,j] is the
range of array indices available to the user if the opponent chooses Vi+1 and [i+1,j-
1] is the range of array indexes available if opponent chooses the jth coin.
• The user chooses the 'jth' coin with value 'Vj': The opponent either chooses 'ith' coin or
'(j-1)th' coin. The opponent intends to choose the coin which leaves the user with
minimum value, i.e. the user can collect the value Vj + min(F(i+1, j-1), F(i, j-2) ) where
[i,j-2] is the range of array indices available for the user if the opponent picks jth
coin and [i+1,j-1] is the range of indices available to the user if the opponent picks
up the ith coin.

Following is the recursive solution that is based on the above two choices. We take a maximum
of two choices.

F(i, j) represents the maximum value the user


can collect from i'th coin to j'th coin.
F(i, j) = Max(Vi + min(F(i+2, j), F(i+1, j-1) ),
Vj + min(F(i+1, j-1), F(i, j-2) ))
As user wants to maximise the number of coins.

Base Cases
F(i, j) = Vi If j == i
F(i, j) = max(Vi, Vj) If j == i + 1

The recursive top down solution in is shown below

// C++ code to implement the approach

#include <bits/stdc++.h>

using namespace std;

vector<int> arr;
map<vector<int>, int> memo;
int n = arr.size();

// recursive top down memoized solution


int solve(int i, int j)
{
if ((i > j) || (i >= n) || (j < 0))
return 0;

vector<int> k{ i, j };
if (memo[k] != 0)
return memo[k];

// if the user chooses ith coin, the opponent can choose


// from i+1th or jth coin. if he chooses i+1th coin,
// user is left with [i+2,j] range. if opp chooses jth
// coin, then user is left with [i+1,j-1] range to
// choose from. Also opponent tries to choose in such a
// way that the user has minimum value left.
int option1
= arr[i]
+ min(solve(i + 2, j), solve(i + 1, j - 1));

// if user chooses jth coin, opponent can choose ith


// coin or j-1th coin. if opp chooses ith coin,user can
// choose in range [i+1,j-1]. if opp chooses j-1th coin,
// user can choose in range [i,j-2].
int option2
= arr[j]
+ min(solve(i + 1, j - 1), solve(i, j - 2));

// since the user wants to get maximum money


memo[k] = max(option1, option2);
return memo[k];
}

int optimalStrategyOfGame()
{

memo.clear();
return solve(0, n - 1);
}

// Driver code
int main()
{
arr.push_back(8);
arr.push_back(15);
arr.push_back(3);
arr.push_back(7);
n = arr.size();
cout << optimalStrategyOfGame() << endl;

arr.clear();
arr.push_back(2);
arr.push_back(2);
arr.push_back(2);
arr.push_back(2);
n = arr.size();
cout << optimalStrategyOfGame() << endl;

arr.clear();
arr.push_back(20);
arr.push_back(30);
arr.push_back(2);
arr.push_back(2);
arr.push_back(2);
arr.push_back(10);
n = arr.size();
cout << optimalStrategyOfGame() << endl;
}

// This code is contributed by phasing17

The bottom up approach is shown below.

// C++ program to find out


// maximum value from a given
// sequence of coins
#include <bits/stdc++.h>
using namespace std;

// Returns optimal value possible


// that a player can collect from
// an array of coins of size n.
// Note than n must be even
int optimalStrategyOfGame(int* arr, int n)
{
// Create a table to store
// solutions of subproblems
int table[n][n];

// Fill table using above


// recursive formula. Note
// that the table is filled
// in diagonal fashion (similar
// to http:// goo.gl/PQqoS),
// from diagonal elements to
// table[0][n-1] which is the result.
for (int gap = 0; gap < n; ++gap) {
for (int i = 0, j = gap; j < n; ++i, ++j) {
// Here x is value of F(i+2, j),
// y is F(i+1, j-1) and
// z is F(i, j-2) in above recursive
// formula
int x = ((i + 2) <= j) ? table[i + 2][j] : 0;
int y = ((i + 1) <= (j - 1))
? table[i + 1][j - 1]
: 0;
int z = (i <= (j - 2)) ? table[i][j - 2] : 0;

table[i][j] = max(arr[i] + min(x, y),


arr[j] + min(y, z));
}
}

return table[0][n - 1];


}

// Driver program to test above function


int main()
{
int arr1[] = { 8, 15, 3, 7 };
int n = sizeof(arr1) / sizeof(arr1[0]);
printf("%d\n", optimalStrategyOfGame(arr1, n));

int arr2[] = { 2, 2, 2, 2 };
n = sizeof(arr2) / sizeof(arr2[0]);
printf("%d\n", optimalStrategyOfGame(arr2, n));

int arr3[] = { 20, 30, 2, 2, 2, 10 };


n = sizeof(arr3) / sizeof(arr3[0]);
printf("%d\n", optimalStrategyOfGame(arr3, n));

return 0;
}

Output
22
4
42

Complexity Analysis:

• Time Complexity: O(n2).


Use of a nested for loop brings the time complexity to n2.
• Auxiliary Space: O(n2).
As a 2-D table is used for storing states.


Egg Dropping Puzzle

The following is a description of the instance of this famous puzzle involving n=2 eggs and a
building with k=36 floors.
Suppose that we wish to know which stories in a 36-storey building are safe to drop eggs from,
and which will cause the eggs to break on landing. We make a few assumptions:
.....An egg that survives a fall can be used again.
.....A broken egg must be discarded.
.....The effect of a fall is the same for all eggs.
.....If an egg breaks when dropped, then it would break if dropped from a higher floor.
.....If an egg survives a fall then it would survive a shorter fall.
.....It is not ruled out that the first-floor windows break eggs, nor is it ruled out that the 36th-floor
do not cause an egg to break.
If only one egg is available and we wish to be sure of obtaining the right result, the experiment
can be carried out in only one way. Drop the egg from the first-floor window; if it survives, drop
it from the second-floor window. Continue upward until it breaks. In the worst case, this method
may require 36 droppings. Suppose 2 eggs are available. What is the least number of egg-
droppings that is guaranteed to work in all cases?
The problem is not actually to find the critical floor, but merely to decide floors from which eggs
should be dropped so that the total number of trials are minimized.

Method 1: Recursion.
In this post, we will discuss a solution to a general problem with 'n' eggs and 'k' floors. The
solution is to try dropping an egg from every floor(from 1 to k) and recursively calculate the
minimum number of droppings needed in the worst case. The floor which gives the minimum
value in the worst case is going to be part of the solution.
In the following solutions, we return the minimum number of trials in the worst case; these
solutions can be easily modified to print floor numbers of every trial also.
Meaning of a worst-case scenario: Worst case scenario gives the user the surety of the threshold
floor. For example- If we have '1' egg and 'k' floors, we will start dropping the egg from the first
floor till the egg breaks suppose on the 'kth' floor so the number of tries to give us surety is 'k'.
1) Optimal Substructure:
When we drop an egg from a floor x, there can be two cases (1) The egg breaks (2) The egg
doesn't break.

1. If the egg breaks after dropping from 'xth' floor, then we only need to check for floors lower
than 'x' with remaining eggs as some floor should exist lower than 'x' in which egg would not
break; so the problem reduces to x-1 floors and n-1 eggs.
2. If the egg doesn't break after dropping from the 'xth' floor, then we only need to check for floors
higher than 'x'; so the problem reduces to 'k-x' floors and n eggs.
Since we need to minimize the number of trials in worst case, we take the maximum of two
cases. We consider the max of above two cases for every floor and choose the floor which yields
minimum number of trials.

k ==> Number of floors


n ==> Number of Eggs
eggDrop(n, k) ==> Minimum number of trials needed to find the critical
floor in worst case.
eggDrop(n, k) = 1 + min{max(eggDrop(n - 1, x - 1), eggDrop(n, k - x)), where x is in {1, 2, ...,
k}}
Concept of worst case:
For example :
Let there be '2' eggs and '2' floors then-:
If we try throwing from '1st' floor:
Number of tries in worst case= 1+max(0, 1)
0=>If the egg breaks from first floor then it is threshold floor (best case possibility).
1=>If the egg does not break from first floor we will now have '2' eggs and 1 floor to test which
will give answer as
'1'.(worst case possibility)
We take the worst case possibility in account, so 1+max(0, 1)=2
If we try throwing from '2nd' floor:
Number of tries in worst case= 1+max(1, 0)
1=>If the egg breaks from second floor then we will have 1 egg and 1 floor to find threshold
floor.(Worst Case)
0=>If egg does not break from second floor then it is threshold floor.(Best Case)
We take worst case possibility for surety, so 1+max(1, 0)=2.
The final answer is min(1st, 2nd, 3rd....., kth floor)
So answer here is '2'.

Below is the implementation of the above approach:

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

// A utility function to get


// maximum of two integers
int max(int a, int b)
{
return (a > b) ? a : b;
}

// Function to get minimum


// number of trials needed in worst
// case with n eggs and k floors
int eggDrop(int n, int k)
{
// If there are no floors,
// then no trials needed.
// OR if there is one floor,
// one trial needed.
if (k == 1 || k == 0)
return k;

// We need k trials for one


// egg and k floors
if (n == 1)
return k;

int min = INT_MAX, x, res;

// Consider all droppings from


// 1st floor to kth floor and
// return the minimum of these
// values plus 1.
for (x = 1; x <= k; x++) {
res = max(
eggDrop(n - 1, x - 1),
eggDrop(n, k - x));
if (res < min)
min = res;
}

return min + 1;
}

// Driver program to test


// to printDups
int main()
{
int n = 2, k = 10;
cout << "Minimum number of trials "
"in worst case with "
<< n << " eggs and " << k
<< " floors is "
<< eggDrop(n, k) << endl;
return 0;
}

// This code is contributed


// by Akanksha Rai

Output
Minimum number of trials in worst case with 2 eggs and 10 floors is 4

Output:

Minimum number of trials in worst


case with 2 eggs and 10 floors is 4

It should be noted that the above function computes the same subproblems again and again. See
the following partial recursion tree, E(2, 2) is being evaluated twice. There will many repeated
subproblems when you draw the complete recursion tree even for small values of n and k.

E(2, 4)
|
-------------------------------------
| | | |
| | | |
x=1/ x=2/ x=3/ x=4/
/ / .... ....
/ /
E(1, 0) E(2, 3) E(1, 1) E(2, 2)
/ /... /
x=1/ .....
/
E(1, 0) E(2, 2)
/
......

Partial recursion tree for 2 eggs and 4 floors.

Complexity Analysis:

• Time Complexity: As there is a case of overlapping sub-problems the time complexity is


exponential.
• Auxiliary Space :O(1). As there was no use of any data structure for storing values.

Since same subproblems are called again, this problem has Overlapping Subproblems property.
So Egg Dropping Puzzle 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 eggFloor[][] in bottom up
manner.
Method 2: Dynamic Programming.
In this approach, we work on the same idea as described above neglecting the case of
calculating the answers to sub-problems again and again.. The approach will be to make a
table which will store the results of sub-problems so that to solve a sub-problem, it would only
require a look-up from the table which will take constant time, which earlier took exponential
time.
Formally for filling DP[i][j] state where 'i' is the number of eggs and 'j' is the number of floors:

• We have to traverse for each floor 'x' from '1' to 'j' and find minimum of:
(1 + max( DP[i-1][j-1], DP[i][j-x] )).

This simulation will make things clear:

i => Number of eggs


j => Number of floors
Look up find maximum
Lets fill the table for the following case:
Floors = '4'
Eggs = '2'
1234
1 2 3 4 => 1
1 2 2 3 => 2
For 'egg-1' each case is the base case so the
number of attempts is equal to floor number.
For 'egg-2' it will take '1' attempt for 1st
floor which is base case.
For floor-2 =>
Taking 1st floor 1 + max(0, DP[1][1])
Taking 2nd floor 1 + max(DP[1][1], 0)
DP[2][2] = min(1 + max(0, DP[1][1]), 1 + max(DP[1][1], 0))
For floor-3 =>
Taking 1st floor 1 + max(0, DP[2][2])
Taking 2nd floor 1 + max(DP[1][1], DP[2][1])
Taking 3rd floor 1 + max(0, DP[2][2])
DP[2][3]= min('all three floors') = 2
For floor-4 =>
Taking 1st floor 1 + max(0, DP[2][3])
Taking 2nd floor 1 + max(DP[1][1], DP[2][2])
Taking 3rd floor 1 + max(DP[1][2], DP[2][1])
Taking 4th floor 1 + max(0, DP[2][3])
DP[2][4]= min('all four floors') = 3

// A Dynamic Programming based for


// the Egg Dropping Puzzle
#include <bits/stdc++.h>
using namespace std;

// A utility function to get


// maximum of two integers
int max(int a, int b)
{
return (a > b) ? a : b;
}

/* Function to get minimum


number of trials needed in worst
case with n eggs and k floors */
int eggDrop(int n, int k)
{
/* A 2D table where entry
eggFloor[i][j] will represent
minimum number of trials needed for
i eggs and j floors. */
int eggFloor[n + 1][k + 1];
int res;
int i, j, x;

// We need one trial for one floor and 0


// trials for 0 floors
for (i = 1; i <= n; i++) {
eggFloor[i][1] = 1;
eggFloor[i][0] = 0;
}

// We always need j trials for one egg


// and j floors.
for (j = 1; j <= k; j++)
eggFloor[1][j] = j;

// Fill rest of the entries in table using


// optimal substructure property
for (i = 2; i <= n; i++) {
for (j = 2; j <= k; j++) {
eggFloor[i][j] = INT_MAX;
for (x = 1; x <= j; x++) {
res = 1 + max(
eggFloor[i - 1][x - 1],
eggFloor[i][j - x]);
if (res < eggFloor[i][j])
eggFloor[i][j] = res;
}
}
}

// eggFloor[n][k] holds the result


return eggFloor[n][k];
}

/* Driver program to test to printDups*/


int main()
{
int n = 2, k = 36;
cout << "\nMinimum number of trials "
"in worst case with " << n<< " eggs and "<< k<<
" floors is "<< eggDrop(n, k);
return 0;
}
// this code is contributed by shivanisinghss2110

Output
Minimum number of trials in worst case with 2 eggs and 36 floors is 8

Complexity Analysis:

• Time Complexity: O(n*k^2).


Where 'n' is the number of eggs and 'k' is the number of floors, as we use a nested for loop 'k^2'
times for each egg
• Auxiliary Space: O(n*k).
As a 2-D array of size 'n*k' is used for storing elements.

Method 3: Dynamic Programming using memoization.

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

vector<vector<int>> memo(MAX, vector<int> (MAX, -1));


int solveEggDrop(int n, int k) {

if(memo[n][k] != -1) { return memo[n][k];}

if (k == 1 || k == 0)
return k;

if (n == 1)
return k;

int min = INT_MAX, x, res;

for (x = 1; x <= k; x++) {


res = max(
solveEggDrop(n - 1, x - 1),
solveEggDrop(n, k - x));
if (res < min)
min = res;
}

memo[n][k] = min+1;
return min + 1;
}

int main() {

int n = 2, k = 36;
cout<<solveEggDrop(n, k);
return 0;
}
// contributed by Shivam Agrawal(shivamagrawal3)

Output
8
Count BSTs with n keys

Given N, Find the total number of unique BSTs that can be made using values from 1 to N.
Examples:

Input: n = 3
Output: 5
For n = 3, preorder traversal of Unique BSTs are:
1. 1 2 3
2. 1 3 2
3. 2 1 3
4. 3 1 2
5. 3 2 1

Input: 4
Output: 14

In this post we will discuss a solution based on Dynamic Programming. For all possible values of
i, consider i as root, then [1....i-1] numbers will fall in the left subtree and [i+1....n] numbers will
fall in the right subtree.

Now, let's say count(n) denotes number of structurally different BST that can be made using
numbers from 1 to n. Then count(n) can be calculated as:-

count(n)=summation of (count(i-1)*count(n-i)).

Below is the implementation for above approach:

// C++ code to find number of unique BSTs


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

// Function to find number of unique BST


int numberOfBST(int n)
{

// DP to store the number of unique BST with key i


int dp[n + 1];
fill_n(dp, n + 1, 0);

// Base case
dp[0] = 1;
dp[1] = 1;

// fill the dp table in bottom-up approach.


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

// n-i in right * i-1 in left


dp[i] = dp[i] + (dp[i - j] * dp[j - 1]);
}
}

return dp[n];
}

// Driver Code
int main()
{
int n = 3;
cout << "Number of structurally Unique BST with " <<
n << " keys are : " << numberOfBST(n) << "\n";

return 0;
}
// This code is contributed by Aditya kumar (adityakumar129)

Output:
Number of structurally Unique BST with 3 keys are : 5

Time Complexity: O(n2)

Space Complexity: O(n)


Maximum sum with no two consecutive

Given an array arr[] of positive numbers, The task is to find the maximum sum of a subsequence
such that no 2 numbers in the sequence should be adjacent in the array.

Examples:

Input: arr[] = {5, 5, 10, 100, 10, 5}


Output: 110
Explanation: Pick the subsequence {5, 100, 5}.
The sum is 110 and no two elements are adjacent. This is the highest possible sum.

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


Output: 13
Explanation: The subsequence is {3, 10}. This gives sum = 13.
This is the highest possible sum of a subsequence following the given criteria

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


Output: 15
Explanation: Pick the subsequence {3, 5, 7}. The sum is 15.

Naive Approach: Below is the idea to solve the problem:

Each element has two choices: either it can be the part of the subsequence with the highest sum
or it cannot be part of the subsequence. So to solve the problem, build all the subsequences of the
array and find the subsequence with the maximum sum such that no two adjacent elements are
present in the subsequence.

Time Complexity: O(2N)


Auxiliary Space: O(N)

Maximum sum such that no two elements are adjacent using Dynamic Programming:

• As seen above, each element has two choices. If one element is picked then its neighbours
cannot be picked. Otherwise, its neighbours may be picked or may not be.
• So the maximum sum till ith index has two possibilities: the subsequence includes arr[i] or it does
not include arr[i].
• If arr[i] is included then the maximum sum depends on the maximum subsequence sum till (i-
1)th element excluding arr[i-1].
• Otherwise, the maximum sum is the same as the maximum subsequence sum till (i-1) where
arr[i-1] may be included or excluded.
So build a 2D dp[N][2] array where dp[i][0] stores maximum subsequence sum till ith index with
arr[i] excluded and dp[i][1] stores the sum when arr[i] is included.
The values will be obtained by the following relations: dp[i][1] = dp[i-1][0] + arr[i] and dp[i][0]
= max(dp[i-1][0], dp[i-1][1])

Follow the steps mentioned below to implement the above idea:

• If the size of the array is 1, then the answer is arr[0].


• Initialize the values of dp[0][0] = 0 and dp[0][1] = arr[0].
• Iterate from i = 1 to N-1:
o Fill the dp array as per the relation shown above.
• Return the maximum between dp[N-1][1] and dp[N-1][0] as that will be the answer.

Below is the implementation of the above approach.

// C++ code to implement the approach

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

// Function to find the maximum sum


int findMaxSum(vector<int> arr, int N)
{
// Declare dp array
int dp[N][2];
if (N == 1) {
return arr[0];
}

// Initialize the values in dp array


dp[0][0] = 0;
dp[0][1] = arr[0];

// Loop to find the maximum possible sum


for (int i = 1; i < N; i++) {
dp[i][1] = dp[i - 1][0] + arr[i];
dp[i][0] = max(dp[i - 1][1],
dp[i - 1][0]);
}

// Return the maximum sum


return max(dp[N - 1][0], dp[N - 1][1]);
}

// Driver Code
int main()
{
// Creating the array
vector<int> arr = { 5, 5, 10, 100, 10, 5 };
int N = arr.size();

// Function call
cout << findMaxSum(arr, N) << endl;
return 0;
}

Output:
110

Time Complexity: O(N)


Auxiliary Space: O(N)

Space Optimized Approach: The above approach can be optimized to be done in constant
space based on the following observation:

As seen from the previous dynamic programming approach, the value of current states (for ith
element) depends upon only two states of the previous element. So instead of creating a 2D
array, we can use only two variables to store the two states of the previous element.

• Say excl stores the value of the maximum subsequence sum till i-1 when arr[i-1] is excluded and
• incl stores the value of the maximum subsequence sum till i-1 when arr[i-1] is included.
• The value of excl for the current state( say excl_new) will be max(excl ,incl). And the value of incl
will be updated to excl + arr[i].

Illustration:

Consider arr[] = {5, 5, 10, 100, 10, 5}

Initially at i = 0: incl = 5, excl = 0

For i = 1: arr[i] = 5
=> excl_new = 5
=> incl = (excl + arr[i]) = 5
=> excl = excl_new = 5

For i = 2: arr[i] = 10
=> excl_new = max(excl, incl) = 5
=> incl = (excl + arr[i]) = 15
=> excl = excl_new = 5

For i = 3: arr[i] = 100


=> excl_new = max(excl, incl) = 15
=> incl = (excl + arr[i]) = 105
=> excl = excl_new = 15

For i = 4: arr[i] = 10
=> excl_new = max(excl, incl) = 105
=> incl = (excl + arr[i]) = 25
=> excl = excl_new = 105

For i = 5: arr[i] = 5
=> excl_new = max(excl, incl) = 105
=> incl = (excl + arr[i]) = 110
=> excl = excl_new = 105

So, answer is max(incl, excl) = 110

Follow the steps mentioned below to implement the above approach:

• Initialize incl and excl with arr[0] and 0 respectively.


• Iterate from i = 1 to N-1:
o Update the values of incl and excl as mentioned above.
• Return the maximum of incl and excl after the iteration is over as the answer.

Below is the implementation of the above approach.

// C++ code to implement the above approach

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

// Function to return max sum such that


// no two elements are adjacent
int FindMaxSum(vector<int> arr, int n)
{
int incl = arr[0];
int excl = 0;
int excl_new;
int i;

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


// Current max excluding i
excl_new = max(incl, excl);

// Current max including i


incl = excl + arr[i];
excl = excl_new;
}

// Return max of incl and excl


return max(incl, excl);
}

// Driver code
int main()
{
vector<int> arr = { 5, 5, 10, 100, 10, 5 };
int N = arr.size();

// Function call
cout << FindMaxSum(arr, N);
return 0;
}
// This approach is contributed by Debanjan

Output
110

Time Complexity: O(N)


Auxiliary Space: O(1).
Subset Sum Problem

Given a set of non-negative integers, and a value sum, determine if there is a subset of the given
set with sum equal to given sum.

Example:

Input: set[] = {3, 34, 4, 12, 5, 2}, sum = 9


Output: True
There is a subset (4, 5) with sum 9.

Input: set[] = {3, 34, 4, 12, 5, 2}, sum = 30


Output: False
There is no subset that add up to 30.

Method 1: Recursion.
Approach: For the recursive approach we will consider two cases.

1. Consider the last element and now the required sum = target sum - value of 'last' element and
number of elements = total elements - 1
2. Leave the 'last' element and now the required sum = target sum and number of elements =
total elements - 1

Following is the recursive formula for isSubsetSum() problem.

isSubsetSum(set, n, sum)
= isSubsetSum(set, n-1, sum) ||
isSubsetSum(set, n-1, sum-set[n-1])
Base Cases:
isSubsetSum(set, n, sum) = false, if sum > 0 and n == 0
isSubsetSum(set, n, sum) = true, if sum == 0

Let's take a look at the simulation of above approach-:

set[]={3, 4, 5, 2}
sum=9
(x, y)= 'x' is the left number of elements,
'y' is the required sum

(4, 9)
{True}
/ \
(3, 6) (3, 9)

/ \ / \
(2, 2) (2, 6) (2, 5) (2, 9)
{True}
/ \
(1, -3) (1, 2)
{False} {True}
/ \
(0, 0) (0, 2)
{True} {False}

// A recursive solution for subset sum problem


#include <iostream>
using namespace std;

// Returns true if there is a subset


// of set[] with sum equal to given sum
bool isSubsetSum(int set[], int n, int sum)
{

// Base Cases
if (sum == 0)
return true;
if (n == 0)
return false;

// If last element is greater than sum,


// then ignore it
if (set[n - 1] > sum)
return isSubsetSum(set, n - 1, sum);

/* else, check if sum can be obtained by any


of the following:
(a) including the last element
(b) excluding the last element */
return isSubsetSum(set, n - 1, sum)
|| isSubsetSum(set, n - 1, sum - set[n - 1]);
}

// Driver code
int main()
{
int set[] = { 3, 34, 4, 12, 5, 2 };
int sum = 9;
int n = sizeof(set) / sizeof(set[0]);
if (isSubsetSum(set, n, sum) == true)
cout <<"Found a subset with given sum";
else
cout <<"No subset with given sum";
return 0;
}

// This code is contributed by shivanisinghss2110

Output
Found a subset with given sum

Complexity Analysis: The above solution may try all subsets of given set in worst case.
Therefore time complexity of the above solution is exponential. The problem is in-fact NP-
Complete (There is no known polynomial time solution for this problem).

Method 2: To solve the problem in Pseudo-polynomial time use the Dynamic programming.
So we will create a 2D array of size (arr.size() + 1) * (target + 1) of type boolean. The state
DP[i][j] will be true if there exists a subset of elements from A[0....i] with sum value = 'j'. The
approach for the problem is:

if (A[i-1] > j)
DP[i][j] = DP[i-1][j]
else
DP[i][j] = DP[i-1][j] OR DP[i-1][j-A[i-1]]

1. This means that if current element has value greater than 'current sum value' we will copy the
answer for previous cases
2. And if the current sum value is greater than the 'ith' element we will see if any of previous states
have already experienced the sum='j' OR any previous states experienced a value 'j - A[i]'
which will solve our purpose.

The below simulation will clarify the above approach:


set[]={3, 4, 5, 2}
target=6

0 1 2 3 4 5 6

0 T F F F F F F

3 T F F T F F F

4 T F F T T F F

5 T F F T T T F

2 T F T T T T T

Below is the implementation of the above approach:

// A Dynamic Programming solution


// for subset sum problem
#include <stdio.h>

// Returns true if there is a subset of set[]


// with sum equal to given sum
bool isSubsetSum(int set[], int n, int sum)
{
// The value of subset[i][j] will be true if
// there is a subset of set[0..j-1] with sum
// equal to i
bool subset[n + 1][sum + 1];

// If sum is 0, then answer is true


for (int i = 0; i <= n; i++)
subset[i][0] = true;

// If sum is not 0 and set is empty,


// then answer is false
for (int i = 1; i <= sum; i++)
subset[0][i] = false;

// Fill the subset table in bottom up manner


for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sum; j++) {
if (j < set[i - 1])
subset[i][j] = subset[i - 1][j];
if (j >= set[i - 1])
subset[i][j] = subset[i - 1][j]
|| subset[i - 1][j - set[i - 1]];
}
}

/* // uncomment this code to print table


for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= sum; j++)
printf ("%4d", subset[i][j]);
printf("\n");
}*/

return subset[n][sum];
}

// Driver code
int main()
{
int set[] = { 3, 34, 4, 12, 5, 2 };
int sum = 9;
int n = sizeof(set) / sizeof(set[0]);
if (isSubsetSum(set, n, sum) == true)
printf("Found a subset with given sum");
else
printf("No subset with given sum");
return 0;
}
// This code is contributed by Arjun Tyagi.

Output
Found a subset with given sum

Complexity Analysis:

Time Complexity: O(sum*n), where sum is the 'target sum' and 'n' is the size of array.
Auxiliary Space: O(sum*n), as the size of 2-D array is sum*n. + O(n) for recursive stack space

Memoization Technique for finding Subset Sum:

Method:

1. In this method, we also follow the recursive approach but In this method, we use another 2-D
matrix in we first initialize with -1 or any negative value.
2. In this method, we avoid the few of the recursive call which is repeated itself that's why we use
2-D matrix. In this matrix we store the value of the previous call value.

Below is the implementation of the above approach:

// CPP program for the above approach


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

// Taking the matrix as globally


int tab[2000][2000];
// Check if possible subset with
// given sum is possible or not
int subsetSum(int a[], int n, int sum)
{

// If the sum is zero it means


// we got our expected sum
if (sum == 0)
return 1;

if (n <= 0)
return 0;

// If the value is not -1 it means it


// already call the function
// with the same value.
// it will save our from the repetition.
if (tab[n - 1][sum] != -1)
return tab[n - 1][sum];

// if the value of a[n-1] is


// greater than the sum.
// we call for the next value
if (a[n - 1] > sum)
return tab[n - 1][sum] = subsetSum(a, n - 1, sum);
else
{

// Here we do two calls because we


// don't know which value is
// full-fill our criteria
// that's why we doing two calls
return tab[n - 1][sum] = subsetSum(a, n - 1, sum) ||
subsetSum(a, n - 1, sum - a[n - 1]);
}
}

// Driver Code
int main()
{
// Storing the value -1 to the matrix
memset(tab, -1, sizeof(tab));
int n = 5;
int a[] = {1, 5, 3, 7, 4};
int sum = 12;

if (subsetSum(a, n, sum))
{
cout << "YES" << endl;
}
else
cout << "NO" << endl;

/* This Code is Contributed by Ankit Kumar*/


}
Output
YES

Complexity Analysis:

• Time Complexity: O(sum*n), where sum is the 'target sum' and 'n' is the size of array.
• Auxiliary Space: O(sum*n) + O(n) -> O(sum*n) = the size of 2-D array is sum*n and
O(n)=auxiliary stack space.
Matrix Chain Multiplication

Given the dimension of a sequence of matrices in an array arr[], where the dimension of the ith
matrix is (arr[i-1] * arr[i]), the task is to find the most efficient way to multiply these matrices
together such that the total number of element multiplications is minimum.

Examples:

Input: arr[] = {40, 20, 30, 10, 30}


Output: 26000
Explanation:There are 4 matrices of dimensions 40x20, 20x30, 30x10, 10x30.
Let the input 4 matrices be A, B, C and D.
The minimum number of multiplications are obtained by
putting parenthesis in following way (A(BC))D.
The minimumm is 20*30*10 + 40*20*10 + 40*10*30

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


Output: 30
Explanation: There are 4 matrices of dimensions 1x2, 2x3, 3x4, 4x3.
Let the input 4 matrices be A, B, C and D.
The minimum number of multiplications are obtained by
putting parenthesis in following way ((AB)C)D.
The minimum number is 1*2*3 + 1*3*4 + 1*4*3 = 30

Input: arr[] = {10, 20, 30}


Output: 6000
Explanation: There are only two matrices of dimensions 10x20 and 20x30.
So there is only one way to multiply the matrices, cost of which is 10*20*30

Matrix Chain Multiplication using Recursion:

We can solve the problem using recursion based on the following facts and observations:

Two matrices of size m*n and n*p when multiplied, they generate a matrix of size m*p and the
number of multiplications performed are m*n*p.

Now, for a given chain of N matrices, the first partition can be done in N-1 ways. For example,
sequence of matrices A, B, C and D can be grouped as (A)(BCD), (AB)(CD) or (ABC)(D) in
these 3 ways.

So a range [i, j] can be broken into two groups like {[i, i+1], [i+1, j]}, {[i, i+2], [i+2, j]}, . . . , {[i,
j-1], [j-1, j]}.
• Each of the groups can be further partitioned into smaller groups and we can find the total
required multiplications by solving for each of the groups.
• The minimum number of multiplications among all the first partitions is the required answer.

Follow the steps mentioned below to implement the approach:

• Create a recursive function that takes i and j as parameters that determines the range of a
group.
o Iterate from k = i to j to partition the given range into two groups.
o Call the recursive function for these groups.
o Return the minimum value among all the partitions as the required minimum number of
multiplications to multiply all the matrices of this group.
• The minimum value returned for the range 0 to N-1 is the required answer.

Below is the implementation of the above approach.

// C++ code to implement the


// matrix chain multiplication using recursion

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

// Matrix Ai has dimension p[i-1] x p[i]


// for i = 1 . . . n
int MatrixChainOrder(int p[], int i, int j)
{
if (i == j)
return 0;
int k;
int mini = INT_MAX;
int count;

// Place parenthesis at different places


// between first and last matrix,
// recursively calculate count of multiplications
// for each parenthesis placement
// and return the minimum count
for (k = i; k < j; k++)
{
count = MatrixChainOrder(p, i, k)
+ MatrixChainOrder(p, k + 1, j)
+ p[i - 1] * p[k] * p[j];

mini = min(count, mini);


}

// Return minimum count


return mini;
}

// Driver Code
int main()
{
int arr[] = { 1, 2, 3, 4, 3 };
int N = sizeof(arr) / sizeof(arr[0]);

// Function call
cout << "Minimum number of multiplications is "
<< MatrixChainOrder(arr, 1, N - 1);
return 0;
}

// This code is contributed by Shivi_Aggarwal

Output
Minimum number of multiplications is 30

The time complexity of the solution is exponential


Auxiliary Space: O(1)

Dynamic Programming Solution for Matrix Chain Multiplication using Memoization:

Below is the recursion tree for the 2nd example of the above recursive approach:

If observed carefully you can find the following two properties:

1) Optimal Substructure: In the above case, we are breaking the bigger groups into smaller
subgroups and solving them to finally find the minimum number of multiplications. Therefore, it
can be said that the problem has optimal substructure property.
2) Overlapping Subproblems: We can see in the recursion tree that the same subproblems are
called again and again and this problem has the Overlapping Subproblems property.

So Matrix Chain Multiplication problem has both properties of a dynamic programming


problem. So recomputations of same subproblems can be avoided by constructing a temporary
array dp[][] in a bottom up manner.

Follow the below steps to solve the problem:

• Build a matrix dp[][] of size N*N for memoization purposes.


• Use the same recursive call as done in the above approach:
o When we find a range (i, j) for which the value is already calculated, return the
minimum value for that range (i.e., dp[i][j]).
o Otherwise, perform the recursive calls as mentioned earlier.
• The value stored at dp[0][N-1] is the required answer.

Below is the implementation of the above approach

// C++ program using memoization


#include <bits/stdc++.h>
using namespace std;
int dp[100][100];

// Function for matrix chain multiplication


int matrixChainMemoised(int* p, int i, int j)
{
if (i == j)
{
return 0;
}
if (dp[i][j] != -1)
{
return dp[i][j];
}
dp[i][j] = INT_MAX;
for (int k = i; k < j; k++)
{
dp[i][j] = min(
dp[i][j], matrixChainMemoised(p, i, k)
+ matrixChainMemoised(p, k + 1, j)
+ p[i - 1] * p[k] * p[j]);
}
return dp[i][j];
}
int MatrixChainOrder(int* p, int n)
{
int i = 1, j = n - 1;
return matrixChainMemoised(p, i, j);
}

// Driver Code
int main()
{
int arr[] = { 1, 2, 3, 4 };
int n = sizeof(arr) / sizeof(arr[0]);
memset(dp, -1, sizeof dp);

cout << "Minimum number of multiplications is "


<< MatrixChainOrder(arr, n);
}

// This code is contributed by Sumit_Yadav

Output
Minimum number of multiplications is 18

Time Complexity: O(N3 )


Auxiliary Space: O(N2) ignoring recursion stack space

Dynamic Programming Solution for Matrix Chain Multiplication using Tabulation (Iterative
Approach):

In iterative approach, we initially need to find the number of multiplications required to multiply
two adjacent matrices. We can use these values to find the minimum multiplication required for
matrices in a range of length 3 and further use those values for ranges with higher lengths.

Build on the answer in this manner till the range becomes [0, N-1].

Follow the steps mentioned below to implement the idea:

• Iterate from l = 2 to N-1 which denotes the length of the range:


o Iterate from i = 0 to N-1:
▪ Find the right end of the range (j) having l matrices.
▪ Iterate from k = i+1 to j which denotes the point of partition.
▪ Multiply the matrices in range (i, k) and (k, j).
▪ This will create two matrices with dimensions arr[i-1]*arr[k] and
arr[k]*arr[j].
▪ The number of multiplications to be performed to multiply these two
matrices (say X) are arr[i-1]*arr[k]*arr[j].
▪ The total number of multiplications is dp[i][k]+ dp[k+1][j] + X.
• The value stored at dp[1][N-1] is the required answer.

Below is the implementation of the above approach.

// See the Cormen book for details of the


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

// Matrix Ai has dimension p[i-1] x p[i]


// for i = 1..n
int MatrixChainOrder(int p[], int n)
{

/* For simplicity of the program, one


extra row and one extra column are
allocated in m[][]. 0th row and 0th
column of m[][] are not used */
int m[n][n];

int i, j, k, L, q;

/* m[i, j] = Minimum number of scalar


multiplications needed to compute the
matrix A[i]A[i+1]...A[j] = A[i..j] where
dimension of A[i] is p[i-1] x p[i] */

// cost is zero when multiplying


// one matrix.
for (i = 1; i < n; i++)
m[i][i] = 0;

// L is chain length.
for (L = 2; L < n; L++)
{
for (i = 1; i < n - L + 1; i++)
{
j = i + L - 1;
m[i][j] = INT_MAX;
for (k = i; k <= j - 1; k++)
{
// q = cost/scalar multiplications
q = m[i][k] + m[k + 1][j]
+ p[i - 1] * p[k] * p[j];
if (q < m[i][j])
m[i][j] = q;
}
}
}

return m[1][n - 1];


}

// Driver Code
int main()
{
int arr[] = { 1, 2, 3, 4 };
int size = sizeof(arr) / sizeof(arr[0]);

cout << "Minimum number of multiplications is "


<< MatrixChainOrder(arr, size);

getchar();
return 0;
}

// This code is contributed


// by Akanksha Rai
Output
Minimum number of multiplications is 18

Time Complexity: O(N3 )


Auxiliary Space: O(N2)
Palindrome Partitioning

Given a string, a partitioning of the string is a palindrome partitioning if every substring of the
partition is a palindrome. For example, "aba|b|bbabb|a|b|aba" is a palindrome partitioning of
"ababbbabbababa". Determine the fewest cuts needed for a palindrome partitioning of a given
string. For example, minimum of 3 cuts are needed for "ababbbabbababa". The three cuts are
"a|babbbab|b|ababa". If a string is a palindrome, then minimum 0 cuts are needed. If a string of
length n containing all different characters, then minimum n-1 cuts are needed.

Examples :

Input : str = "geek"


Output : 2
We need to make minimum 2 cuts, i.e., "g ee k"
Input : str = "aaaa"
Output : 0
The string is already a palindrome.
Input : str = "abcde"
Output : 4
Input : str = "abbac"
Output : 1

This problem is a variation of Matrix Chain Multiplication problem. If the string is a palindrome,
then we simply return 0. Else, like the Matrix Chain Multiplication problem, we try making cuts
at all possible places, recursively calculate the cost for each cut and return the minimum value.
Let the given string be str and minPalPartion() be the function that returns the fewest cuts needed
for palindrome partitioning. following is the optimal substructure property.

Using Recursion

// i is the starting index and j is the ending index. i must be passed as 0


and j as n-1
minPalPartion(str, i, j) = 0 if i == j. // When string is of length 1.
minPalPartion(str, i, j) = 0 if str[i..j] is palindrome.

// If none of the above conditions is true, then minPalPartion(str, i, j) can


be
// calculated recursively using the following formula.
minPalPartion(str, i, j) = Min { minPalPartion(str, i, k) + 1 +
minPalPartion(str, k+1, j) }
where k varies from i to j-1
// C++ Code for Palindrome Partitioning
// Problem
#include <bits/stdc++.h>
using namespace std;

bool isPalindrome(string String, int i, int j)


{
while(i < j)
{
if(String[i] != String[j])
return false;
i++;
j--;
}
return true;
}
int minPalPartion(string String, int i, int j)
{
if( i >= j || isPalindrome(String, i, j) )
return 0;
int ans = INT_MAX, count;
for(int k = i; k < j; k++)
{
count = minPalPartion(String, i, k) +
minPalPartion(String, k + 1, j) + 1;

ans = min(ans, count);


}
return ans;
}
// Driver code
int main() {
string str = "ababbbabbababa";
cout << "Min cuts needed for " <<
"Palindrome Partitioning is " <<
minPalPartion(str, 0, str.length() - 1) << endl;
return 0;
}
// This code is contributed by rag2127

Output:

Min cuts needed for Palindrome Partitioning is 3

Time Complexity: O(2n)

Auxiliary Space: O(n)


Using Dynamic Programming :
Following is Dynamic Programming solution. It stores the solutions to subproblems in two
arrays P[][] and C[][], and reuses the calculated values.
// Dynamic Programming Solution for
// Palindrome Partitioning Problem
#include <bits/stdc++.h>
using namespace std;

// Returns the minimum number of cuts


// needed to partition a string
// such that every part is a palindrome
int minPalPartion(string str)
{
// Get the length of the string
int n = str.length();

/* Create two arrays to build the solution


in bottom up manner
C[i][j] = Minimum number of cuts needed for
palindrome partitioning
of substring str[i..j]
P[i][j] = true if substring str[i..j] is
palindrome, else false
Note that C[i][j] is 0 if P[i][j] is true */
int C[n][n];
bool P[n][n];

// Every substring of length 1 is a palindrome


for (int i = 0; i < n; i++) {
P[i][i] = true;
C[i][i] = 0;
}

/* L is substring length. Build the


solution in bottom up manner by
considering all substrings of
length starting from 2 to n.
The loop structure is same as Matrix
Chain Multiplication problem
( See https:// www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/
)*/
for (int L = 2; L <= n; L++) {

// For substring of length L, set


// different possible starting indexes
for (int i = 0; i < n - L + 1; i++) {
int j = i + L - 1; // Set ending index

// If L is 2, then we just need to


// compare two characters. Else
// need to check two corner characters
// and value of P[i+1][j-1]
if (L == 2)
P[i][j] = (str[i] == str[j]);
else
P[i][j] = (str[i] == str[j]) && P[i + 1][j - 1];

// IF str[i..j] is palindrome, then C[i][j] is 0


if (P[i][j] == true)
C[i][j] = 0;
else {

// Make a cut at every possible


// location starting from i to j,
// and get the minimum cost cut.
C[i][j] = INT_MAX;
for (int k = i; k <= j - 1; k++)
C[i][j] = min(C[i][j], C[i][k] + C[k + 1][j] + 1);
}
}
}

// Return the min cut value for


// complete string. i.e., str[0..n-1]
return C[0][n - 1];
}

// Driver code
int main()
{
string str = "ababbbabbababa";
cout << "Min cuts needed for Palindrome"
" Partitioning is "
<< minPalPartion(str);
return 0;
}

// This code is contributed by rathbhupendra

Output:

Min cuts needed for Palindrome Partitioning is 3

Time Complexity: O(n3)

Auxiliary Space: O(n2)

We can optimize the above code a bit further. Instead of calculating C[i] separately in O(n^2),
we can do it with the P[i] itself. Below is the highly optimized code of this problem:

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

int minCut(string a)
{
int cut[a.length()];
bool palindrome[a.length()][a.length()];
memset(palindrome, false, sizeof(palindrome));
for (int i = 0; i < a.length(); i++)
{
int minCut = i;
for (int j = 0; j <= i; j++)
{
if (a[i] == a[j] && (i - j < 2 || palindrome[j + 1][i - 1]))
{
palindrome[j][i] = true;
minCut = min(minCut, j == 0 ? 0 : (cut[j - 1] + 1));
}
}
cut[i] = minCut;
}
return cut[a.length() - 1];
}

// Driver code
int main()
{
cout << minCut("aab") << endl;
cout << minCut("aabababaxx") << endl;
return 0;
}

// This code is contributed by divyesh072019.

Time Complexity: O(n2)

Auxiliary Space: O(n2)

An optimization to above approach


In the above approach, we can calculate the minimum cut while finding all palindromic
substring. If we find all palindromic substring 1st and then we calculate minimum cut, time
complexity will reduce to O(n2).
Thanks for Vivek for suggesting this optimization.

// Dynamic Programming Solution for Palindrome Partitioning Problem


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

// A utility function to get minimum of two integers


int min(int a, int b) { return (a < b) ? a : b; }

// Returns the minimum number of cuts needed to partition a string


// such that every part is a palindrome
int minPalPartion(char* str)
{

// Get the length of the string


int n = strlen(str);

/* Create two arrays to build the solution in bottom-up manner


C[i] = Minimum number of cuts needed for a palindrome partitioning
of substring str[0..i]
P[i][j] = true if substring str[i..j] is palindrome, else false
Note that C[i] is 0 if P[0][i] is true */
int C[n];
bool P[n][n];

int i, j, k, L; // different looping variables

// Every substring of length 1 is a palindrome


for (i = 0; i < n; i++) {
P[i][i] = true;
}

/* L is substring length. Build the solution in bottom up manner by


considering all substrings of length starting from 2 to n. */
for (L = 2; L <= n; L++) {
// For substring of length L, set different possible starting indexes
for (i = 0; i < n - L + 1; i++) {
j = i + L - 1; // Set ending index

// If L is 2, then we just need to compare two characters. Else


// need to check two corner characters and value of P[i+1][j-1]
if (L == 2)
P[i][j] = (str[i] == str[j]);
else
P[i][j] = (str[i] == str[j]) && P[i + 1][j - 1];
}
}

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


if (P[0][i] == true)
C[i] = 0;
else {
C[i] = INT_MAX;
for (j = 0; j < i; j++) {
if (P[j + 1][i] == true && 1 + C[j] < C[i])
C[i] = 1 + C[j];
}
}
}

// Return the min cut value for complete string. i.e., str[0..n-1]
return C[n - 1];
}

// Driver program to test above function


int main()
{
char str[] = "ababbbabbababa";
cout <<"Min cuts needed for Palindrome Partitioning is " <<
minPalPartion(str);
return 0;
}

// This code is contributed by shivanisinghss2110

Output:
Min cuts needed for Palindrome Partitioning is 3

Time Complexity: O(n2)

Auxiliary Space: O(n2)

Using Memorization to solve this problem.


The basic idea is to cache the intermittent results calculated in recursive functions. We can put
these results into a hashmap/unordered_map.
To calculate the keys for the Hashmap we will use the starting and end index of the string as the
key i.e. ["start_index".append("end_index")] would be the key for the Hashmap.

Below is the implementation of above approach :

// Using memoizatoin to solve the partition problem.


#include <bits/stdc++.h>
using namespace std;
// Function to check if input string is palindrome or not
bool ispalindrome(string input, int start, int end)
{
// Using two pointer technique to check palindrome
while (start < end) {
if (input[start] != input[end])
return false;
start++;
end--;
}
return true;
}

// Function to find keys for the Hashmap


string convert(int a, int b)
{
return to_string(a) + "" + to_string(b);
}

// Returns the minimum number of cuts needed to partition a string


// such that every part is a palindrome
int minpalparti_memo(string input, int i, int j, unordered_map<string, int>&
memo)
{
if (i > j)
return 0;
// Key for the Input String
string ij = convert(i, j);

// If the no of partitions for string "ij" is already calculated


// then return the calculated value using the Hashmap
if (memo.find(ij) != memo.end()) {
return memo[ij];
}
// Every String of length 1 is a palindrome
if (i == j) {
memo[ij] = 0;
return 0;
}
if (ispalindrome(input, i, j)) {
memo[ij] = 0;
return 0;
}
int minimum = INT_MAX;
// Make a cut at every possible location starting from i to j
for (int k = i; k < j; k++) {
int left_min = INT_MAX;
int right_min = INT_MAX;
string left = convert(i, k);
string right = convert(k + 1, j);

// If left cut is found already


if (memo.find(left) != memo.end()) {
left_min = memo[left];
}
// If right cut is found already
if (memo.find(right) != memo.end()) {
right_min = memo[right];
}

// Recursively calculating for left and right strings


if (left_min == INT_MAX)
left_min = minpalparti_memo(input, i, k, memo);
if (right_min == INT_MAX)
right_min = minpalparti_memo(input, k + 1, j, memo);

// Taking minimum of all k possible cuts


minimum = min(minimum, left_min + 1 + right_min);
}

memo[ij] = minimum;
// Return the min cut value for complete string.
return memo[ij];
}
int main()
{
string input = "ababbbabbababa";
unordered_map<string, int> memo;
cout << minpalparti_memo(input, 0, input.length() - 1, memo) << endl;
return 0;
}

Time Complexity: O(n3)

Auxiliary Space: O(n2)


Allocate Minimum Pages

Given a number of pages in N different books and M students. The books are arranged in
ascending order of the number of pages. Every student is assigned to read some consecutive
books. The task is to assign books in such a way that the maximum number of pages assigned to
a student is minimum.

Example :

Input : pages[] = {12, 34, 67, 90} , m = 2


Output : 113
Explanation: There are 2 number of students. Books can be distributed in following fashion :
1) [12] and [34, 67, 90]
Max number of pages is allocated to student ‘2’ with 34 + 67 + 90 = 191 pages
2) [12, 34] and [67, 90] Max number of pages is allocated to student ‘2’ with 67 + 90 = 157
pages
3) [12, 34, 67] and [90] Max number of pages is allocated to student ‘1’ with 12 + 34 + 67 =
113 pages

Of the 3 cases, Option 3 has the minimum pages = 113.

Naive Approach:

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

int sum(int arr[],int b, int e){


int s=0;
for(int i=b;i<=e;i++)
s+=arr[i];
return s;
}

int minPages(int arr[],int n, int k){


if(k==1)
return sum(arr,0,n-1);
if(n==1)
return arr[0];
int res=INT_MAX;
for(int i=1;i<n;i++){
res=min(res,max(minPages(arr,i,k-1),sum(arr,i,n-1)));
}
return res;
}

int main()
{
int arr[]={10,20,10,30};
int n=sizeof(arr)/sizeof(arr[0]);
int k=2;

cout<<minPages(arr,n,k);
}

Output:
40

Approach: A Binary Search method for solving the book allocation problem:

Case 1: When no valid answer exists.

If the number of students is greater than the number of books (i.e, M > N), In this case at least 1
student will be left to which no book has been assigned.

Case 2: When a valid answer exists.

The maximum possible answer could be when there is only one student. So, all the book will be
assigned to him and the result would be the sum of pages of all the books.

The minimum possible answer could be when number of student is equal to the number of book
(i.e, M == N) , In this case all the students will get at most one book. So, the result would be the
maximum number of pages among them (i.e, minimum(pages[])).

Hence, we can apply binary search in this given range and each time we can consider the mid
value as the maximum limit of pages one can get. And check for the limit if answer is valid then
update the limit accordingly.

Below is the implementation of the above idea:

• Initialise the start to minimum(pages[]) and end = sum of pages[],


• Do while start <= end
o Calculate the mid and check if mid number of pages can assign any student by
satisfying the given condition such that all students will get at least one book.
Follow the steps to check for validity.
▪ Initialise the studentsRequired = 1 and curr_sum = 0 for sum of
consecutive pages of book
▪ Iterate over all books or say pages[]
▪ Add the pages to curr_sum and check curr_sum > curr_min then
increment the count of studentRequired by 1.
▪ Check if the studentRequired > M, return false.
▪ Return true.
o If mid is valid then, update the result and move the end = mid – 1
o Otherwise, move the start = mid + 1
• Finally, return the result.
Below is the implementation of the above approach:

// C++ program for optimal allocation of pages


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

// Utility function to check if current minimum value


// is feasible or not.
bool isPossible(int arr[], int n, int m, int curr_min)
{
int studentsRequired = 1;
int curr_sum = 0;

// iterate over all books


for (int i = 0; i < n; i++) {
// check if current number of pages are greater
// than curr_min that means we will get the result
// after mid no. of pages
if (arr[i] > curr_min)
return false;

// count how many students are required


// to distribute curr_min pages
if (curr_sum + arr[i] > curr_min) {
// increment student count
studentsRequired++;

// update curr_sum
curr_sum = arr[i];

// if students required becomes greater


// than given no. of students,return false
if (studentsRequired > m)
return false;
}

// else update curr_sum


else
curr_sum += arr[i];
}
return true;
}

// function to find minimum pages


int findPages(int arr[], int n, int m)
{
long long sum = 0;

// return -1 if no. of books is less than


// no. of students
if (n < m)
return -1;

// Count total number of pages


for (int i = 0; i < n; i++)
sum += arr[i];
// initialize start as 0 pages and end as
// total pages
int start = 0, end = sum;
int result = INT_MAX;

// traverse until start <= end


while (start <= end) {
// check if it is possible to distribute
// books by using mid as current minimum
int mid = (start + end) / 2;
if (isPossible(arr, n, m, mid)) {
// update result to current distribution
// as it's the best we have found till now.
result = mid;

// as we are finding minimum and books


// are sorted so reduce end = mid -1
// that means
end = mid - 1;
}

else
// if not possible means pages should be
// increased so update start = mid + 1
start = mid + 1;
}

// at-last return minimum no. of pages


return result;
}

// Drivers code
int main()
{
// Number of pages in books
int arr[] = { 12, 34, 67, 90 };
int n = sizeof arr / sizeof arr[0];
int m = 2; // No. of students

cout << "Minimum number of pages = "


<< findPages(arr, n, m) << endl;
return 0;
}

Output

Minimum number of pages = 113

Time Complexity: O(N*log(N)), Where N is the total number of pages in the book.
Auxiliary Space: O(1)

You might also like