The set of all possible subproblem parameters are known
The relation between the table positions and subproblems is established
Another approach is to memoize
by using hashing with subproblem parameters as key
CE100 Algorithms and Programming II
Dynamic Programming vs Memoization Summary (1)
Matrix-chain multiplication can be solved in O(n3) time
by either a top-down memoized recursive algorithm
or a bottom-up dynamic programming algorithm
Both methods exploit the overlapping subproblems property
There are only Θ(n2) different subproblems in total
Both methods compute the soln to each problem once
Without memoization the natural recursive algorithm runs in exponential time since subproblems are solved repeatedly
CE100 Algorithms and Programming II
Dynamic Programming vs Memoization Summary (2)
In general practice
If all subproblems must be solved at once
a bottom-up DP algorithm always outperforms a top-down memoized algorithm by a constant factor
because, bottom-up DP algorithm
Has no overhead for recursion
Less overhead for maintaining the table
DP:Regular pattern of table accesses can be exploited to reduce the time and/or space requirements even further
Memoized: If some problems need not be solved at all, it has the advantage of avoiding solutions to those subproblems
CE100 Algorithms and Programming II
Problem 3: Longest Common Subsequence
Definitions
A subsequence of a given sequence is just the given sequence with some elements (possibly none) left out
Example:
X=⟨A,B,C,B,D,A,B⟩
Z=⟨B,C,D,B⟩
Z is a subsequence of X
CE100 Algorithms and Programming II
Problem 3: Longest Common Subsequence
Definitions
Formal definition: Given a sequence X=⟨x1,x2,…,xm⟩, sequence Z=⟨z1,z2,…,zk⟩ is a subsequence of X
if ∃ a strictly increasing sequence⟨i1,i2,…,ik⟩ of indices of X such that xij=zj for all j=1,2,…,k, where 1≤k≤m
Example:Z=⟨B,C,D,B⟩ is a subsequence of X=⟨A,B,C,B,D,A,B⟩ with the index sequence⟨i1,i2,i3,i4⟩=⟨2,3,5,7⟩
CE100 Algorithms and Programming II
Problem 3: Longest Common Subsequence
Definitions
If Z is a subsequence of both X and Y, we denote Z as a common subsequence of X and Y.
Example:
XY=⟨A,B∗,C∗,B,D,A∗,B⟩=⟨B∗,D,C∗,A∗,B,A⟩
Z1=⟨B∗,C∗,A∗⟩ is a common subsequence (of length 3) of X and Y.
Two longest common subsequence (LCSs) of X and Y?
Z2=⟨B,C,B,A⟩ of length 4
Z3=⟨B,D,A,B⟩ of length 4
The optimal solution value = 4
CE100 Algorithms and Programming II
Longest Common Subsequence (LCS) Problem
LCS problem: Given two sequences
X=⟨x1,x2,…,xm⟩ and
Y=⟨y1,y2,…,yn⟩, find the LCS of X&Y
Brute force approach:
Enumerate all subsequences of X
Check if each subsequence is also a subsequence of Y
Keep track of the LCS
What is the complexity?
There are 2m subsequences of X
Exponential runtime
CE100 Algorithms and Programming II
Notation
Notation: Let Xi denote the ith prefix of X
i.e. Xi=⟨x1,x2,…,xi⟩
Example:
XX4X0=⟨A,B,C,B,D,A,B⟩=⟨A,B,C,B⟩=⟨⟩
CE100 Algorithms and Programming II
Optimal Substructure of an LCS
Let X=<x1,x2,…,xm> and Y=⟨y1,y2,…,yn⟩ are given
Let Z=⟨z1,z2,…,zk⟩ be an LCS of X and Y
Question 1: If xm=yn, how to define the optimal substructure?
We must have zk=xm=yn and
Zk−1=LCS(Xm−1,Yn−1)
CE100 Algorithms and Programming II
Optimal Substructure of an LCS
Let X=<x1,x2,…,xm> and Y=⟨y1,y2,…,yn⟩ are given
Let Z=⟨z1,z2,…,zk⟩ be an LCS of X and Y
Question 2: If xm=ynandzk=xm, how to define the optimal substructure?
We must have Z=LCS(Xm−1,Y)
CE100 Algorithms and Programming II
Optimal Substructure of an LCS
Let X=<x1,x2,…,xm> and Y=⟨y1,y2,…,yn⟩ are given
Let Z=⟨z1,z2,…,zk⟩ be an LCS of X and Y
Question 3: If xm=ynandzk=yn, how to define the optimal substructure?
We must have Z=LCS(X,Yn−1)
CE100 Algorithms and Programming II
Theorem: Optimal Substructure of an LCS
Let X=⟨x1,x2,…,xm⟩ and Y = <y1, y2, …, yn> are given
Let Z=⟨z1,z2,…,zk⟩ be an LCS of X and Y
Theorem: Optimal substructure of an LCS:
If xm=yn
then zk=xm=yn and Zk−1 is an LCS of Xm−1 and Yn−1
If xm=yn and zk=xm
then Z is an LCS of Xm−1 and Y
If xm=yn and zk=yn
then Z is an LCS of X and Yn−1
CE100 Algorithms and Programming II
Optimal Substructure Theorem (case 1)
If xm=yn then zk=xm=yn and Zk−1 is an LCS of Xm−1 and Yn−1
CE100 Algorithms and Programming II
Optimal Substructure Theorem (case 2)
If xm=yn and zk=xm then Z is an LCS of Xm−1 and Y
CE100 Algorithms and Programming II
Optimal Substructure Theorem (case 3)
If xm=yn and zk=yn then Z is an LCS of X and Yn−1
CE100 Algorithms and Programming II
Proof of Optimal Substructure Theorem (case 1)
If xm=yn then zk=xm=yn and Zk−1 is an LCS of Xm−1 and Yn−1
Proof: If zk=xm=yn then
we can append xm=yn to Z to obtain a common subsequence of length k+1⟹contradiction
Thus, we must have zk=xm=yn
Hence, the prefix Zk−1 is a length-(k−1) CS of Xm−1 and Yn−1
We have to show thatZk−1 is in fact an LCS of Xm−1 and Yn−1
Proof by contradiction:
Assume that∃ a CS W of Xm−1 and Yn−1 with ∣W∣=k
Then appending xm=yn to W produces a CS of length k+1
CE100 Algorithms and Programming II
Proof of Optimal Substructure Theorem (case 2)
If xm=yn and zk=xm then Z is an LCS of Xm−1 and Y
Proof : If zk=xm then Z is a CS of Xm−1 and Yn
We have to show thatZ is in fact an LCS of Xm−1 and Yn
(Proof by contradiction)
Assume that ∃ a CS W of Xm−1 and Yn with ∣W∣>k
Then W would also be a CS of X and Y
Contradiction to the assumption that
Z is an LCS of X and Y with ∣Z∣=k
Case 3: Dual of the proof for (case 2)
CE100 Algorithms and Programming II
A Recursive Solution to Subproblems
Theorem implies that there are one or two subproblems to examine
ifxm=ynthen
we must solve the subproblem of finding an LCS of Xm−1&Yn−1
appending xm=yn to this LCS yields an LCS of X&Y
else
we must solve two subproblems
finding an LCS of Xm−1&Y
finding an LCS of X&Yn−1
longer of these two LCS s is an LCS of X&Y
endif
CE100 Algorithms and Programming II
Recursive Algorithm (Inefficient)
LCS(X,Y){m←length[X]n←length[Y]ifxm=ynthenZ←LCS(Xm−1,Yn−1)▹solve one subproblemreturn⟨Z,xm=yn⟩▹appendxm=yntoZelseZ′←LCS(Xm−1,Y)▹solve two subproblemsZ′′←LCS(X,Yn−1)return longer ofZ′andZ′′}
Total Space=Θ(mn)Total Runtime=Θ(mn)⎩⎨⎧LCS−LENGTH(X,Y)m←length[X];n←length[Y]fori←0tomdoc[i,0]←0forj←0tondoc[0,j]←0fori←1tomdoforj←1tondoifxi=yjthenc[i,j]←c[i−1,j−1]+1b[i,j]←"↖"else ifc[i−1,j]≥c[i,j−1]c[i,j]←c[i−1,j]b[i,j]←"↑"elsec[i,j]←c[i,j−1]b[i,j]←"←"
CE100 Algorithms and Programming II
Computing the Length of an LCS-1
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-2
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-3
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-4
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-5
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-6
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-7
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-8
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-9
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-10
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-11
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-12
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
CE100 Algorithms and Programming II
Computing the Length of an LCS-13
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
Running-time = O(mn) since each table entry takes O(1) time to compute
CE100 Algorithms and Programming II
Computing the Length of an LCS-14
Operation of LCS-LENGTH on the sequences
XY=⟨A1,B2,C3,B4,D5,A6,B7⟩=⟨B1,D2,C3,A4,B5,A6⟩
Running-time = O(mn) since each table entry takes O(1) time to compute
LCS of X&Y=⟨B,C,B,A⟩
CE100 Algorithms and Programming II
Constructing an LCS
The b table returned by LCS-LENGTH can be used to quickly construct an LCS of X&Y
Begin at b[m,n] and trace through the table following arrows
Whenever you encounter a "↖" in entry b[i,j] it implies that xi=yj is an element of LCS
The elements of LCS are encountered in reverse order
CE100 Algorithms and Programming II
Constructing an LCS
The recursive procedure PRINT-LCS prints out LCS in proper order
This procedure takes O(m+n) time since at least one of i and j is decremented in each stage of the recursion
Example: If we search for keyword "while", we need
to access 3 nodes. So, 23 of the queries will have cost of 3.
Total Cost=i∑(depth(i)+1)freq(i)=1×0.04+2×0.4+2×0.1+3×0.05+3×0.08+3×0.1+3×0.23=2.42
CE100 Algorithms and Programming II
Cost of a Binary Search Tree
Example: If we search for keyword "while", we need
to access 3 nodes. So, 23 of the queries will have cost of 3.
Total Cost=i∑(depth(i)+1)freq(i)=1×0.4+2×0.05+2×0.23+3×0.1+4×0.08+4×0.1+5×0.04=2.18
This is in fact an optimal BST.
CE100 Algorithms and Programming II
Optimal Binary Search Tree Problem
Given:
A collection of n keys K1<K2<…Kn to be stored in a BST.
The corresponding pi values for 1≤i≤n
pi: probability of searching for key Ki
Find:
An optimal BST with minimum total cost:
Total Cost=i∑(depth(i)+1)freq(i)
Note: The BST will be static. Only search operations will be performed. No insert, no delete, etc.
CE100 Algorithms and Programming II
Cost of a Binary Search Tree
Lemma 1: Let Tij be a BST containing keys Ki<Ki+1<⋯<Kj. Let TL and TR be the left and right subtrees of T. Then we have:
cost(Tij)=cost(TL)+cost(TR)+h=i∑jph
Intuition: When we add the root node, the depth of each node in TL and TR increases by 1. So, the cost of node h increases by ph. In addition, the cost of root node r is pr. That’s why, we have the last term at the end of the formula above.
CE100 Algorithms and Programming II
Optimal Substructure Property
Lemma 2: Optimal substructure property
Consider an optimal BSTTij for keys Ki<Ki+1<⋯<Kj
Let Km be the key at the root of Tij
Then:
Ti,m−1 is an optimal BST for subproblem containing keys:
Ki<⋯<Km−1
Tm+1,j is an optimal BST for subproblem containing keys:
Km+1<⋯<Kj
cost(Tij)=cost(Ti,m−1)+cost(Tm+1,j)+h=i∑jph
CE100 Algorithms and Programming II
Recursive Formulation
Note:We don’t know which root vertex leads to the minimum total cost. So, we need to try each vertex m, and choose the one with minimum total cost.
c[i,j]: cost of an optimal BST Tij for the subproblem Ki<⋯<Kj
Overlapping Subproblems Property in Dynamic Programming
Dynamic Programming is an algorithmic paradigm that solves a given complex problem by breaking it into subproblems and stores the results of subproblems to avoid computing the same results again.
CE100 Algorithms and Programming II
Overlapping Subproblems Property in Dynamic Programming
Following are the two main properties of a problem that suggests that the given problem can be solved using Dynamic programming.
Overlapping Subproblems
Optimal Substructure
CE100 Algorithms and Programming II
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.
CE100 Algorithms and Programming II
Overlapping Subproblems
For example, Binary Search doesn’t have common subproblems.
If we take an example of following recursive program for Fibonacci Numbers, there are many subproblems that are solved again and again.
CE100 Algorithms and Programming II
Simple Recursion
f(n)=f(n−1)+f(n−2)
C sample code:
#include<stdio.h>// a simple recursive program to compute fibonacci numbersintfib(int n){
if (n <= 1)
return n;
elsereturn fib(n-1) + fib(n-2);
}
intmain(){
int n = 5;
printf("Fibonacci number is %d ", fib(n));
return0;
}
CE100 Algorithms and Programming II
Simple Recursion
Output
Fibonacci number is 5
CE100 Algorithms and Programming II
Simple Recursion
f(n)=f(n−1)+f(n−2)
/* a simple recursive program for Fibonacci numbers */publicclassFibonacci{
publicstaticvoidmain(String[] args){
int n = Integer.parseInt(args[0]);
System.out.println(fib(n));
}
publicstaticintfib(int n){
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
}
CE100 Algorithms and Programming II
Simple Recursion
f(n)=f(n−1)+f(n−2)
publicclassFibonacci {
publicstaticvoidMain(string[] args) {
int n = int.Parse(args[0]);
Console.WriteLine(fib(n));
}
publicstaticintfib(int n) {
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
}
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.
CE100 Algorithms and Programming II
Recursion tree for execution of fib(5)
There are following two different ways to store the values so that these values can be reused:
Memoization (Top Down)
Tabulation (Bottom Up)
CE100 Algorithms and Programming II
Memoization (Top Down)
The memoized program for a problem is similar to the recursive version with a small modification that 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.
CE100 Algorithms and Programming II
Memoization (Top Down)
Following is the memoized version for the nth Fibonacci Number.
C++ Version:
/* C++ program for Memoized version
for nth Fibonacci number */#include<bits/stdc++.h>usingnamespace std;
#define NIL -1#define MAX 100int lookup[MAX];
CE100 Algorithms and Programming II
Memoization (Top Down)
C++ Version:
/* Function to initialize NIL
values in lookup table */void _initialize()
{
int i;
for (i = 0; i < MAX; i++)
lookup[i] = NIL;
}
CE100 Algorithms and Programming II
Memoization (Top Down)
C++ Version:
/* function for nth Fibonacci number */intfib(int n){
if (lookup[n] == NIL) {
if (n <= 1)
lookup[n] = n;
else
lookup[n] = fib(n - 1) + fib(n - 2);
}
return lookup[n];
}
CE100 Algorithms and Programming II
Memoization (Top Down)
C++ Version:
// Driver codeintmain(){
int n = 40;
_initialize();
cout << "Fibonacci number is " << fib(n);
return0;
}
CE100 Algorithms and Programming II
Memoization (Top Down)
Java Version:
/* Java program for Memoized version */publicclassFibonacci{
finalint MAX = 100;
finalint NIL = -1;
int lookup[] = newint[MAX];
/* Function to initialize NIL values in lookup table */void_initialize(){
for (int i = 0; i < MAX; i++)
lookup[i] = NIL;
}
CE100 Algorithms and Programming II
Memoization (Top Down)
Java Version:
/* function for nth Fibonacci number */intfib(int n){
if (lookup[n] == NIL) {
if (n <= 1)
lookup[n] = n;
else
lookup[n] = fib(n - 1) + fib(n - 2);
}
return lookup[n];
}
CE100 Algorithms and Programming II
Memoization (Top Down)
Java Version:
publicstaticvoidmain(String[] args){
Fibonacci f = new Fibonacci();
int n = 40;
f._initialize();
System.out.println("Fibonacci number is"
+ " " + f.fib(n));
}
}
CE100 Algorithms and Programming II
Memoization (Top Down)
C# Version:
// C# program for Memoized versionof nth Fibonacci numberusing System;
classFiboCalcMemoized {
staticint MAX = 100;
staticint NIL = -1;
staticint[] lookup = newint[MAX];
/* Function to initialize NIL
values in lookup table */staticvoidinitialize()
{
for (int i = 0; i < MAX; i++)
lookup[i] = NIL;
}
CE100 Algorithms and Programming II
Memoization (Top Down)
C# Version:
/* function for nth Fibonacci number */staticintfib(int n)
{
if (lookup[n] == NIL) {
if (n <= 1)
lookup[n] = n;
else
lookup[n] = fib(n - 1) + fib(n - 2);
}
return lookup[n];
}
CE100 Algorithms and Programming II
Memoization (Top Down)
C# Version:
// Driver codepublicstaticvoidMain()
{
int n = 40;
initialize();
Console.Write("Fibonacci number is"
+ " " + fib(n));
}
}
CE100 Algorithms and Programming II
Tabulation (Bottom Up)
The tabulated program for a given problem builds a table in bottom-up fashion and returns the last entry from the 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.
CE100 Algorithms and Programming II
Tabulation (Bottom Up)
C++ Version:
/* C program for Tabulated version */#include<stdio.h>intfib(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];
}
CE100 Algorithms and Programming II
Tabulation (Bottom Up)
C++ Version:
...
intmain(){
int n = 9;
printf("Fibonacci number is %d ", fib(n));
return0;
}
Output:
Fibonacci number is 34
CE100 Algorithms and Programming II
Tabulation (Bottom Up)
Java Version:
/* Java program for Tabulated version */publicclassFibonacci{
publicstaticvoidmain(String[] args){
int n = 9;
System.out.println("Fibonacci number is " + fib(n));
}
CE100 Algorithms and Programming II
Tabulation (Bottom Up)
Java Version:
/* Function to calculate nth Fibonacci number */staticintfib(int n){
int f[] = newint[n + 1];
f[0] = 0;
f[1] = 1;
for (int i = 2; i <= n; i++)
f[i] = f[i - 1] + f[i - 2];
return f[n];
}
}
CE100 Algorithms and Programming II
Tabulation (Bottom Up)
C# Version:
// C# program for Tabulated versionusing System;
classFibonacci {
staticintfib(int n)
{
int[] f = newint[n + 1];
f[0] = 0;
f[1] = 1;
for (int i = 2; i <= n; i++)
f[i] = f[i - 1] + f[i - 2];
return f[n];
}
publicstaticvoidMain()
{
int n = 9;
Console.Write("Fibonacci number is"
+ " " + fib(n));
}
}
CE100 Algorithms and Programming II
Both Tabulated and Memoized store the solutions of subproblems.
In Memoized version, the table is filled on demand while in the 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.
CE100 Algorithms and Programming II
To see the optimization achieved by Memoized and Tabulated solutions over the basic Recursive solution, see the time taken by following runs for calculating the 40th Fibonacci number:
Optimal Substructure Property in Dynamic Programming
A given problems has Optimal Substructure Property if optimal solution of the given problem can be obtained by using optimal solutions of its subproblems.
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 algorithm like Floyd–Warshall and Single Source Shortest path algorithm for negative weight edges like Bellman–Ford are typical examples of Dynamic Programming.
CE100 Algorithms and Programming II
Optimal Substructure Property in 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 two nodes
CE100 Algorithms and Programming II
Optimal Substructure Property in Dynamic Programming
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 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.
CE100 Algorithms and Programming II
Most Common Dynamic Programming Interview Questions