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

Algorithms and Problem Solving (15B11CI411) : EVEN 2022

The document discusses the dynamic programming approach to solve the coin change problem and longest common subsequence problem. It provides examples and pseudocode implementations of dynamic programming algorithms to find the minimum number of coins needed to make a given sum and to find the length of the longest common subsequence between two strings. The coin change problem algorithm uses a 2D table M to store the minimum coins for each sum, and the LCS problem algorithm uses a 2D table L to store the length of the LCS for each substring pair.

Uploaded by

Vivek Garg
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

Algorithms and Problem Solving (15B11CI411) : EVEN 2022

The document discusses the dynamic programming approach to solve the coin change problem and longest common subsequence problem. It provides examples and pseudocode implementations of dynamic programming algorithms to find the minimum number of coins needed to make a given sum and to find the length of the longest common subsequence between two strings. The coin change problem algorithm uses a 2D table M to store the minimum coins for each sum, and the LCS problem algorithm uses a 2D table L to store the length of the LCS for each substring pair.

Uploaded by

Vivek Garg
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

Algorithms and Problem Solving (15B11CI411)

EVEN 2022

Design Technique: Dynamic


Programming

Jaypee Institute of Information Technology (JIIT)


A-10, Sector 62, Noida
Coin Change Problem
• Given a list of coins i.e 1 cents, 5 cents and 10 cents, we have to make an amount by using
these coins such that a minimum number of coins are used.
• Example: sum = 8 and coins= {1, 5, 10}. 2 ways: {1 + 1 + 1 + 1 + 1 + 1 + 1 + 1} and {1 + 1 + 1 + 5}.
• Let's start by picking up the first coin i.e., the coin with the value d1.
• So, we now need to make the value of sum−d1 and Msum−d1 is the minimum number of
coins needed for this purpose.
• So, the total number of coins needed are 1+Msum−d1 (1 coin already picked).
• Similarly, we can pick the second coin first and then attempt to get the optimal solution for the
value of sum−d2 which will require Msum−d2 coins and thus a total of 1+Msum−d2 .
• We can repeat the process with all the n coins and then the minimum value of all these will be
our answer. i.e., mini:di≤n{1+Msum−di }.
d[] = {0, 1, 2, 3} \\ first value is 0, we have n=3 denominations

Using Dynamic Programming Sum = 5

M[0]=0
int coin_change(int d[], int sum, int n) { J=1: min = inf
i=1: 1=1  min=1+M[0] = 1
int M[sum+1];
i=2: 1<2
M[0] = 0; i=3: 1<3
int i, j; M[1]=1
for(j=1; j<=sum; j++) { J=2: min = inf
i=1: 2>1  min=1+M[1] = 2
int min = INF; i=2: 2=2  min=1+M[0] = 1
for(i=1; i<=n; i++) { i=3: 2<3
if(j >= d[i]) { M[2]=1
min = MIN(min, 1+M[j-d[i]]); J=3: min = inf
i=1: 3>1  min=1+M[2] = 2
} i=2: 3>2  min=1+M[1] = 2
} i=3: 3=3  min=1+M[0] = 1
M[j] = min; M[3]=1
J=4: min = inf
}
i=1: 4>1  min=1+M[3] = 2
return M[n]; i=2: 4>2  min=1+M[2] = 2
} i=3: 4>3  min=1+M[1] = 2
M[4]=2
J=5: min = inf
i=1: 5>1  min=1+M[4] = 3
T(n) = O(n*sum)
i=2: 5>2  min=1+M[3] = 2
i=3: 5>3  min=1+M[2] = 2
How to obtain which coins are in the solution?
d[] = {0, 1, 2, 3} \\ first value is 0, we have n=3 denominations
int coin_change_modified(int d[], int sum, int n) { Sum = 5
int M[sum+1]; M[0] = 0;
int S[sum+1]; S[0] = 0; M[0]=0, S[0]=0
int i, j; J=1: min = inf, coin=0
for(j=1; j<=sum; j++) { i=1: 1=1  min=1+M[0] = 1, coin=1
int min = INF; i=2: 1<2
i=3: 1<3
int coin=0; M[1]=1, S[1]=1
for(i=1; i<=n; i++) { J=2: min = inf , coin=0
if(j >= d[i]) { i=1: 2>1  min=1+M[1] = 2 , coin=1
if((1+M[j-d[i]]) < min) { i=2: 2=2  min=1+M[0] = 1 , coin=2
min = 1+M[j-d[i]]; i=3: 2<3
coin = i; M[2]=1 , S[2]=2
J=3: min = inf , coin=0
}
i=1: 3>1  min=1+M[2] = 2 , coin=1
} i=2: 3>2  min=1+M[1] = 2 ,
} i=3: 3=3  min=1+M[0] = 1 , coin=3
M[j] = min; M[3]=1 , S[3]=3
S[j] = coin; J=4: min = inf , coin=0
} i=1: 4>1  min=1+M[3] = 2 , coin=1
int l = sum; i=2: 4>2  min=1+M[2] = 2
i=3: 4>3  min=1+M[1] = 2
while(l>0) {
M[4]=2 , S[4]=1
printf("%d\n",d[S[l]]); J=5: min = inf , coin=0
l = l-d[S[l]]; i=1: 5>1  min=1+M[4] = 3 , coin=1
} i=2: 5>2  min=1+M[3] = 2 , coin=2
return M[n]; i=3: 5>3  min=1+M[2] = 2
} M[5] = 2 , S[5]=2
Print 2, 3
Longest Common Subsequence problem: Given two strings, X and Y, the task is to find the length
of the longest subsequence present in both of the strings.
Example:
L() G X T X A Y B
For X = “AGGTAB”, Y = “GXTXAYB”
A 0 0 0 0 1 0 0
Output: 4 (GTAB) G 1 1 1 1 1 1 1
Let input X[0…m-1], Y[0…n-1] and G 1 1 1 1 1 1 1
T 0 1 2 2 2 2 2
L(X[0…m-1], Y[0…n-1]) be the length of the LCS of X and Y. A 0 1 2 2 3 3 3
B 0 1 2 2 3 3 4

Recurssive solution:
• If the last characters of both sequences match (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 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]) )
int lcs(string X, string Y, int m, int n)
{
int L[m + 1][n + 1];

// 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++) {


G X T X A Y B
for (int j = 0; j <= n; j++) {
L 0 1 2 2 3 4 5 6
if (i == 0 || j == 0) 0 0 0 0 0 0 0 0 0
L[i][j] = 0; A 1 0 0 0 0 0 1 0 0
G 2 0 1 1 1 1 1 1 1
else if (X[i - 1] == Y[j - 1])
L[i][j] = 1 + L[i - 1][j – 1]; G 3 0 1 1 1 1 1 1 1
T 4 0 0 1 2 2 2 2 2
else A 5 0 0 1 2 2 3 3 3
L[i][j] = max(L[i - 1][j], L[i][j - 1]); B 6 0 0 1 2 2 3 3 4
}
}
// Following code is used to print LCS G X T X A Y B
int index = L[m][n]; L 0 1 2 3 4 5 6 7
// Create a character array to store the lcs string
0 0 0 0 0 0 0 0 0
char lcs[index + 1];
A 1 0 0 0 0 0 1 0 0
lcs[index] = '\0'; // Set the terminating character
G 2 0 1 1 1 1 1 1 1
// Start from the right-most-bottom-most corner
int i = m, j = n; G 3 0 1 1 1 1 1 1 1
while (i > 0 && j > 0) { T 4 0 0 1 2 2 2 2 2
// If current character in X and Y are same, then current character is part of LCS A 5 0 0 1 2 2 3 3 3
if (X[i - 1] == Y[j - 1]) { B 6 0 0 1 2 2 3 3 4
lcs[index - 1] = X[i - 1]; // Put current character in result
i--; j--; index--; // reduce values of i, j and index Index=4
} lcs[5]='\0'
// If not same, then find the larger of two and go in the direction of larger value i=7, j=8
X[6] == Y[7]  lcs[4] = X[6] = ‘B’
else if (L[i - 1][j] > L[i][j - 1]) i=6, j=7, index=4;
i--; X[5] ! = Y[6]  L[5][7] !> L[6][6]  j=6
else X[5] = = Y[5]  lcs[3]=X[5] = ‘A’
i=5, j=5, index=3;
j--; X[4] ! = Y[4]  L[4][5] !> L[5][4]  j=4
} X[4] = = Y[3]  lcs[2]=X[4] = ‘T’
i=4, j=3, index=2;
// Print the lcs X[3] ! = Y[2]  L[3][3] !> L[4][2]  j=3
X[3] == Y[1]  lcs[1]=X[3] = ‘G’
cout << "LCS of " << X << " and " << Y << " is " << lcs; i=3, j=1, index=1;
X[2] ! = Y[0]  L[2][1] > L[3][0]  i=1 ….
Longest Increasing Subsequence problem: Given an integer array, return the length of the longest
strictly increasing subsequence.
Example 1: A = [10,9,2,5,3,7,101,18]; Output: 4 (LIS is [2,3,7,101])
Example 2: A = [7,7,7,7,7,7,7]; Output: 1 (LIS is [7])
Let L(i) be the length of the LIS of ending at index i such that A[i] is the last element of the LIS.
To find the LIS for a given array, we need to return max(L(i)) where 0 < i < n
• A = {5, 12, 3, 15}
int lis(int A[], int n) {
• L = {1, 1, 1, 1} int L[n];
• A[2] > A[1]  L[2] = max(L[2], L[1]+1) = 2 L[0] = 1;
• A[3] < A[1] //no change // Compute optimized LIS values in bottom up manner
• A[3] < A[2] //no change for (int i = 1; i < n; i++) {
• A[4] > A[1]  L[4] = max(L[4], L[1]+1) = 2 L[i] = 1;
for (int j = 0; j < i; j++)
• A[4] > A[2]  L[4] = max(L[4], L[2]+1) = 3
if (A[i] > A[j] && L[i] < L[j] + 1)
• A[4] > A[3]  L[4] = max(L[4], L[3]+1) = 3 L[i] = L[j] + 1;
}
L(i) = 1 + max(L(j)) where 0 <j< i and A[j]<A[i] // Return maximum value in L[]
L(i) = 1, if no such j exists. return *max_element(L, L + n);
} T(n) = O(n^2)
Another Solution: A = [10,9,2,5,3,7,101,18];
Sort and store in B = [2,3,5,7,9,10,18,101];
LCS(A,B) = [2,3,7,18]; Output: 4 // this is the LIS(A) T(n) = O(n^2)
Another approach:
Let A = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13}
Step 1: L1 = {0} // start a new list and n1=|L1|
Step 2: A[1]>L1[n1-1]  L1 = {0}, L2 = {0,8} //Clone and extend the existing list
Step 3: A[2]<L2[n1-1] // no change
A[2]>L1[n1-1]  L1 = {0}, L3 = {0,4} //Clone and extend the existing list
Lists are L1 = {0}, L3={0,4} //Discard L2 which is of same length as L3 with last element 8>4
Rules:
1. If A[i] is smallest among all end candidates of current lists, we will start new current list of length 1.
2. If A[i] is largest among all end candidates of current lists, we will clone the largest current 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.
Rules:
1. If A[i] is smallest among all end candidates of current lists, we will start new current list of length 1.
2. If A[i] is largest among all end candidates of current lists, we will clone the largest current 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.

Let A = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13}


Step 3: A[2]<L2[n1-1] // no change
A[2]>L1[n1-1]  L1 = {0}, L3 = {0,4} //Clone and extend
Lists are L1 = {0}, L3={0,4} //Discard L2 which is of same length as L3 with last element 8>4
Step 4: A[4]>L1[n1-1] and A[4]>L3[n3-1]  L1 = {0}, L3 = {0,4}, L4 = {0,4,12} //Clone and extend
Step 5: A[5]> L1[n1-1]  L1 = {0}, L5 = {0,2} //Clone and extend
Lists are L1 = {0}, L5={0,2}, L4 = {0,4,12} //Discard L3 as 4>2
Step 6: A[6]> L5[n5-1]  L5 = {0,2}, L6 = {0,2,10} //Clone and extend
Lists are L1 = {0}, L5={0,2}, L6 = {0,2,10} //Discard L4 as 12>10
Rules:
1. If A[i] is smallest among all end candidates of current lists, we will start new current list of length 1.
2. If A[i] is largest among all end candidates of current lists, we will clone the largest current 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.

Let A = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13}


Step 6: A[6]> L5[n5-1]  L5 = {0,2}, L6 = {0,2,10} //Clone and extend
Lists are L1 = {0}, L5={0,2}, L6 = {0,2,10} //Discard L4 as 12>10
Step 7: A[7]> L5[n5-1]  L5 = {0,2}, L7 = {0,2,6} //Clone and extend
Lists are L1 = {0}, L5={0,2}, L7 = {0,2,6} //Discard L6 as 10>6
Step 8: A[8]>L1[n1-1],L5[n5-1], L7[n7-1]  L1 = {0}, L5={0,2}, L7 = {0,2,6}, L8 = {0,2,6,14] //Clone and extend
Step 9: A[9]>L1[n1-1]  L1 = {0}, L9 = {0,1} //Clone and extend
Lists are L1 = {0}, L9={0,1}, L7 = {0,2,6}, L8 = {0,2,6,14] //Discard L5 as 2>1

Ans: LIS= {0, 2, 6, 9, 13}
Minimum Steps Problem: On a positive integer, you are allowed to perform any one of the
following 3 steps.
• 1) Subtract 1 from it. ( n = n - 1 ) ,
• 2) If its divisible by 2, divide by 2. ( if n % 2 == 0 , then n = n / 2 ) ,
• 3) If its divisible by 3, divide by 3. ( if n % 3 == 0 , then n = n / 3 ).
Given a positive integer n, find the minimum number of steps that takes n to 1.
Example: int getMinSteps ( int n )
For n = 1 , output: 0 {
int ns[n+1], i;
For n = 4 , output: 2 ( 4 /2 = 2 /2 = 1 ) ns[1] = 0; // trivial case
For n = 7 , output: 3 ( 7 -1 = 6 /3 = 2 /2 = 1 ) for( i = 2 ; i < = n ; i ++ )
{
Recurssive solution: ns[i] = 1 + ns[i-1];
if(i%2==0)
F(n) = 1 + min{ F(n-1), F(n/2), F(n/3)}, if (n>1)
ns[i] = min(ns[i] , 1+ ns[i/2] );
0, otherwise
if(i%3==0)
ns[i] = min(ns[i] , 1+ ns[i/3] );
}
return ns[n];
}
Edit Distance Problem
• Given two strings A and B, find the minimum number of steps required to convert A to B. (each
operation is counted as 1 step). The 3 operations permitted on a word are Insert a character, Delete a
character, and Replace a character
• Input: A = “abad”, B = “abac” Output: 1 (Operation 1: Replace d with c)
• Input: A = “horse”, B = “ro” Output: 3

Recursive solution:
• Consider two pointers i and j pointing the end of given string A and B.
• If the current character, pointing in both the strings are same, then no changes are to be made.
Therefore, recurse for lengths i - 1 and j - 1.
• Otherwise, try to apply the operations provided.
• Each of the given operations would cause 1 units.
• For insertion: Recurse i to j-1.
• For deletion: Recurse i-1 to j .
• For replacement: Recurse i – 1 to j – 1.
• After applying all the operations, f(i, j) = 1 + min(f(i, j-1), f(i-1, j), f(i – 1, j – 1)).
int editDist(string str1, string str2, int i, int j) Example 1: str1 = llo, str2 = hello
i=3,j=5  i=2,j=4  i=1,j=3  i=0,j=2
{
i  o, j  o: i l, j  l: i l, j l: return 2 (he will be
// If first string is empty, the only option is to insert all characters of inserted in str1 to make it hello)
second string into first or vice-versa
if (i == 0) Example 2: str1 = hollo, str2 = hello
i=5,j=5  i=4,j=4  i=3,j=3  i=2,j=2
return j; i  o, j  o: i l, j  l: i l, j l:
if (j == 0) return 1+ min(eD(2,1), eD(1,2), eD(1,1)) = 1+0 = 1 (e
will be replaced for o)
return i;
eD(2,1): i=2,j=1  1+min(eD(2,0),eD(1,1),eD(1,0))
/* If last characters of two strings are same, ignore last characters and 1+min(2, eD(0,0), 1)
get count for remaining strings */ 1+min(2, 0, 1) = 1
if (str1[i - 1] == str2[j - 1]) eD(1,2): i=1,j=2  1+min(eD(1,1),eD(0,2),eD(0,1))
1+min(0, 2, 1) = 1
return editDist(str1, str2, i - 1, j - 1);
eD(1,1) = 0
/* 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. */
T(i) = O(3^i) – worst case when none of the
return 1 + min(editDist(str1, str2, i, j - 1), // Insert characters of both the strings match
editDist(str1, str2, i - 1, j), // Remove
editDist(str1, str2, i – 1, j - 1) // Replace
); Overlapping Subproblems  Dynamic Programming

}
void EditDistDP(string str1, string str2) {
int len1 = str1.length(); int len2 = str2.length();
int DP[2][len1 + 1];
memset(DP, 0, sizeof DP);
for (int i = 0; i <= len1; i++)
DP[0][i] = i;
// For every character in second string
for (int i = 1; i <= len2; i++) {
for (int j = 0; j <= len1; j++) {
// if first string is empty then add character 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. i % 2 is to 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]));
}
}
}
// 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;
}
Few more practice problems:
• Print all possible ways to convert one string into another string.
• 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.
• Hint: C(n, k) = C(n-1, k-1) + C(n-1, k) and C(n, 0) = C(n, n) = 1
• Given n eggs and k floors, find the minimum number of trials needed in worst case to find critical floor (the
floor below which all floors are safe). A floor is safe if dropping an egg from it does not break the egg.
• K  Number of floors, N  Number of Eggs, and 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}
• eggDrop(3,3) = 1+min{max(eggDrop(2,0), eggDrop(3,1)),max(eggDrop(2,1),eggDrop(3,1)),max(eggDrop(2,2),eggDrop(3,0))}
= 1+min{1,1,2} = 2
eggDrop(2,0) = 0, eggDrop(3,1) = 1, eggDrop(2, 1) = 1,eggDrop(3,1) = 1, eggDrop(3,0) = 0
eggDrop(2,2) = 1+min{max{(eggDrop(1,0), eggDrop(2,1)),(eggDrop(2, 1),eggDrop(2,0))}} = 1+min{max{(0,1),(1,0)} = 2
Few more practice problems:
• Longest palindromic subsequence. Example: MAYAAM has “MAYAM” or “MAAAM”
• X[0..n-1]  input sequence, L(0, n-1)  length of the longest palindromic subsequence of X[0..n-1].
If last and first characters of X are same, then L(0, n-1) = L(1, n-2) + 2
Else L(0, n-1) = MAX (L(1, n-1), L(0, n-2))

• Partition problem: Determine whether a given set can be partitioned into two subsets such that the sum of
elements in both subsets is the same. E.g. {1, 5, 11, 5} can be partitioned as {1, 5, 5} and {11}
• isPossible(arr, n, sum/2) be the function that returns true if there is a subset of arr[0..n-1] with sum equal
to sum/2
• Divide the problem into two subproblems:
• isPossible() without considering last element (reducing n to n-1)
• isPossible() considering the last element (reducing sum/2 by arr[n-1] and n to n-1)
• isPossible (arr, n, sum/2) = isPossible (arr, n-1, sum/2) || isPossible (arr, n-1, sum/2 – arr[n-1])

You might also like