Daniel Sutantyo
Department of Computing
Macquarie University
public static int fib(int n) {
if (n <= 2)
return 1;
else return fib(n-1) + fib(n-2);
}
(the obligatory dynamic programming example)
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
public static int fib(int n) {
if (n <= 2)
return 1;
else return fib(n-1) + fib(n-2);
}
fib(10)
fib(9)
fib(8)
fib(8)
fib(7)
fib(7)
fib(6)
fib(6)
fib(6)
fib(5)
fib(6)
fib(5)
fib(5)
fib(4)
fib(7)
fib(6)
fib(5)
...
...
...
...
number recursive calls
public static int fib(int n) {
if (n <= 2)
return 1;
else return fib(n-1) + fib(n-2);
}
n | fib(n) | no. of recursive calls |
---|---|---|
10 | 55 | 109 |
20 | 6,765 | 13,529 |
30 | 832,040 | 1,664,079 |
40 | 102,334,155 | 204,668,309 |
50 | 12,586,269,025 | 25,172,538,049 |
60 | ??? | ??? |
top down dynamic programming
(memoisation)
public static long table[] = new long[1000];
public static int fib(int n) {
if (table[n] != 0)
return table[n];
else {
table[n] = fib2(n-1) + fib2(n-2);
return table[n];
}
}
top down dynamic programming
(tabulation)
fib(10)
fib(9)
fib(8)
fib(8)
fib(7)
fib(6)
fib(6)
fib(5)
fib(7)
...
bottom-up dynamic programming
public static long fib(int n) {
long a = 0;
long b = 1;
for (int i = 0; i < n; i++) {
b = a + b;
a = b - a;
}
return a;
}
recursion
top-down
DP
bottom-up
DP
???
dynamic programming vs divide-and-conquer
dynamic programming vs divide-and-conquer
public static int fib(int n) {
if (n <= 2)
return 1;
else return fib(n-1) + fib(n-2);
}
dynamic programming vs divide-and-conquer
fib(n)
fib(n-1)
fib(n-2)
linear_search(0,n)
linear_search(0,0)
linear_search(1,n)
binary_search(0,n)
binary_search(0,n/2)
binary_search(n/2+1,n)
dynamic programming vs divide-and-conquer
fib(n)
fib(n-1)
fib(n-2)
merge_sort(0,n)
merge_sort(0,n/2)
merge_sort(n/2+1,n)
dynamic programming vs divide-and-conquer
fib(n)
fib(n-1)
fib(n-2)
m_sort(0,n)
m_sort(0,n/2)
m_sort(n/2+1,n)
fib(n-2)
fib(n-3)
fib(n-3)
fib(n-4)
m_sort(0,n/4)
m_sort(n/4+1,n/2)
m_sort(n/2+1,3n/4)
m_sort(3n/4+1,n)
dynamic programming vs divide-and-conquer
fib(n)
fib(n-1)
fib(n-2)
fib(n-2)
fib(n-3)
fib(n-3)
fib(n-4)
dynamic programming vs divide-and-conquer
optimal substructure
We have a bunch of metallic bars with known lengths. Sometimes we need to produce a bar of a certain length from the bars that we have.
We are not allowed to cut up any metallic bar, but we can solder any two bars together to create a longer bar.
[ 50, 2, 18, 11, 9, 23, 5, 10, 30, 6, 17 ] L = 27
[ 2, 18, 11, ..., 17 ] L = -23
[ 2, 18, 11, ..., 17 ] L = 27
don't pick 50
pick 50
[18, 11, ... , 17] L = -25
don't pick 2
pick 2
pick 2
don't pick 2
...
...
...
...
...
...
[18, 11, ... , 17] L = -23
[18, 11, ... , 17] L = 25
[18, 11, ... , 17] L = 27
...
...
[ 50, 2, 18, 11, 9, 23, 5, 10, 30, 6, 17 ] L = 27
[ 2, 18, 11, 9, 23, ..., 17 ] L = -23
pick 50
[ 50, 18, 11, 9, 23, ..., 17 ] L = 25
pick 2
[ 50, 2, 11, 9, 23, ..., 17 ] L = 7
[ 50, 2, 18, 9, 23, ..., 17 ] L = 16
pick 18
pick 11
[ 50, 2, 18, 11, 9, 23, ..., 6 ] L = 10
pick 17
pick 9
[ 50, 2, 18, 11, 23, ..., 17 ] L = 16
...
...
...
subproblems
subproblem: [ 50, 2, 18, 11, 9, 23, 5, 30, 6, 17 ] L = 17
optimal solution: 1 number (pick 17)
non-optimal solutions: 2 numbers (pick 11, 6)
pick 2
pick 10
subproblem: [ 50, 2, 18, 11, 9, 23, 5, 10, 30, 6 ] L = 25
optimal solution: 3 (pick 11, 9, 5)
problem: [ 50, 2, 18, 11, 9, 23, 5, 10, 30, 6, 17 ] L = 27
optimal solution: 2 (pick 17 and 10)
the optimal solution to the problem contains
the optimal solution to the subproblem
...
...
...
subproblems
subproblem: [ 50, 2, 18, 11, 9, 23, 5, 30, 6, 17 ] L = 17
optimal solution: 1 number (pick 17)
non-optimal solutions: 2 numbers (pick 11, 6)
problem: [ 50, 2, 18, 11, 9, 23, 5, 10, 30, 6, 17 ] L = 27
optimal solution: 2 (pick 17 and 10)
How do you construct the optimal solution to the problem using the optimal solutions of the subproblems?
Optimal substructure (informally): The optimal solution is 1 + the best among the optimal solutions of the subproblems
subproblems
We have a bunch of metallic bars with known lengths. Sometimes we need to produce a bar of a certain length from the bars that we have.
We are not allowed to cut up any metallic bar, but we can solder any two bars together to create a longer bar.
We have metallic bars with known lengths as before, but for each length, you now have an infinite number of bars with that length.
We need to produce a bar of a certain length from the bars that we have. As before, you cannot cut up any metallic bar, but can solder any two bars together to create a longer bar.
[ 100, 50, 25, 10, 5, 1 ] L = 374
pick 100
pick 50
pick 25
pick 10
[ 100, 50, 25, 10, 5, 1 ] L = 373
pick 1
pick 5
[ 100, 50, 25, 10, 5, 1 ] L = 274
[ 100, 50, 25, 10, 5, 1 ] L = 369
[ 100, 50, 25, 10, 5, 1 ] L = 364
[ 100, 50, 25, 10, 5, 1 ] L = 349
[ 100, 50, 25, 10, 5, 1 ] L = 324
[ 100, 50, 25, 10, 5, 1 ] L = 374
pick 100
pick 50
pick 25
pick 10
[ 100, 50, 25, 10, 5, 1 ] L = 373
pick 1
pick 5
[ 100, 50, 25, 10, 5, 1 ] L = 274
[ 100, 50, 25, 10, 5, 1 ] L = 369
[ 100, 50, 25, 10, 5, 1 ] L = 364
[ 100, 50, 25, 10, 5, 1 ] L = 349
[ 100, 50, 25, 10, 5, 1 ] L = 324
Do you have overlapping subproblems?
[ 100, 50, 25, 10, 5, 1 ] L = 374
Do you have overlapping subproblems?
Pick 100 : subproblem [ 100, 50, 25, 10, 5, 1 ] L = 274
Pick 50 twice : subproblem [ 100, 50, 25, 10, 5, 1 ] L = 274
Pick 25 four times : subproblem [ 100, 50, 25, 10, 5, 1 ] L = 274
Pick 1, 1, 1, 1, 1, 5, 5, 5, 5, 25, 25, 5, 5, 5, 5, then pick 5 :
subproblem [ 100, 50, 25, 10, 5, 1 ] L = 274
[ 100, 50, 25, 10, 5, 1 ] L = 374
pick 100
pick 50
pick 25
pick 10
[ 100, 50, 25, 10, 5, 1 ] L = 373
pick 1
pick 5
[ 100, 50, 25, 10, 5, 1 ] L = 274
[ 100, 50, 25, 10, 5, 1 ] L = 369
[ 100, 50, 25, 10, 5, 1 ] L = 364
[ 100, 50, 25, 10, 5, 1 ] L = 349
[ 100, 50, 25, 10, 5, 1 ] L = 324
Do you have optimal substructure?
[ 100, 50, 25, 10, 5, 1 ] L = 374
pick 100
pick 50
pick 25
pick 10
[ 100, 50, 25, 10, 5, 1 ] L = 373
pick 1
pick 5
[ 100, 50, 25, 10, 5, 1 ] L = 274
[ 100, 50, 25, 10, 5, 1 ] L = 369
[ 100, 50, 25, 10, 5, 1 ] L = 364
[ 100, 50, 25, 10, 5, 1 ] L = 349
[ 100, 50, 25, 10, 5, 1 ] L = 324
How do you construct the optimal solution to the problem using the optimal solutions of the subproblems?
Optimal substructure (informally): the optimal solution is 1 + the best among the optimal solutions of the subproblems
problems with optimal substructure
problems with optimal substructure
5
4
1
3
2
problems without optimal substructure
5
4
1
3
2
problems without optimal substructure
5
4
1
3
5
1
3
2
problems without optimal substructure
why does it matter
why does it matter
can we solve it?
developing a DP algorithm
developing a DP algorithm
\(i\)
\(p_i\)
1 2 3 4 5 6 7 8 9 10
1 5 8 9 10 17 17 20 24 30
\(i\)
\(p_i\)
1 2 3 4 5 6 7 8 9 10
1 5 8 9 10 17 17 20 24 30
brute force
4
1+3
2+2
1+1+2
3+1
1+2+1
2+1+1
1+1+1+1
brute force
5,
4+1, 3+2, 2+3, 1+4
3+1+1, 1+3+1, 1+1+3, 2+2+1, 2+1+2, 1+2+2
1+1+1+2, 1+1+2+1, 1+2+1+1, 2+1+1+1
1+1+1+1+1
brute force
\(c_1\)
\(c_2\)
\(c_3\)
\(c_4\)
brute force
\(c_1\)
\(c_2\)
\(c_3\)
\(c_4\)
overlapping subproblems
overlapping subproblems
overlapping subproblems
optimal substructure
Step 1: show that there is an optimal substructure
Step 1: show that there is an optimal substructure
Step 1: show that there is an optimal substructure
Step 1: show that there is an optimal substructure
Step 1: show that there is an optimal substructure
$10
$1 + $8
$8 + $1
$9+$0
$10
Step 1: show that there is an optimal substructure
$11
$1 + $8
$8 + $1
$9+$0
$11
Step 1: show that there is an optimal substructure
showing optimal substructure
showing optimal substructure
Step 2: show the recursive relation that gives optimal solution
Step 2: show the recursive relation that gives optimal solution
Step 2: show the recursive relation that gives optimal solution
Step 2: show the recursive relation that gives optimal solution
\[r_n = \max_{1\le i \le n} (p_i + r_{n-i}) \]
Step 3: compute the value of an optimal solution
\[r_n = \max_{1\le i \le n} (p_i + r_{n-i}) \]
// assume p[i] gives price of rod of length i
public static int cut(int n) {
if (n == 0)
return 0;
int answer = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
answer = Math.max(answer, p[i] + cut(n-i));
}
return answer;
}
Step 3: compute the value of an optimal solution
static int[] p = {0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30,
33, 36, 40, 42, 45, 50, 52, 58, 58, 60,
62, 65, 66, 72, 80, 82, 83, 85, 87, 88};
public static int cut(int n) {
if (n == 0)
return 0;
int answer = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
answer = Math.max(answer, p[i] + cut(n-i));
}
return answer;
}
n | optimal answer | no. of recursive calls |
---|---|---|
10 | 30 | 1,024 |
15 | 45 | 33,792 |
20 | 63 | 1,082,368 |
25 | 80 | 34,636,800 |
30 | 94 | 1,108,378,624 |
static int[] p = {0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30,
33, 36, 40, 42, 45, 50, 52, 58, 58, 60,
62, 65, 66, 72, 80, 82, 83, 85, 87, 88};
public static int cut(int n) {
if (n == 0)
return 0;
int answer = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
answer = Math.max(answer, p[i] + cut(n-i));
}
return answer;
}
public static int cut(int n) {
if (r[n] != 0) return r[n]; // memoise
if (n == 0)
return 0;
int answer = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
answer = Math.max(answer, p[i] + cut(n-i));
}
return r[n] = answer; // store the result
}
Step 3: compute the value of an optimal solution
public static int cut(int n) {
if (r[n] != 0) return r[n]; // memoise
if (n == 0)
return 0;
int answer = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
answer = Math.max(answer, p[i] + cut(n-i));
}
return r[n] = answer; // store the result
}
n | optimal answer | no. of recursive calls |
---|---|---|
10 | 30 | 56 |
15 | 45 | 122 |
20 | 63 | 213 |
25 | 80 | 329 |
30 | 94 | 470 |
recursion
top-down
DP
bottom-up
DP
???
Step 3: compute the value of an optimal solution
public static int cut_bottom_up(int n) {
r[0] = 0;
for (int j = 1; j <= n; j++) {
int answer = Integer.MIN_VALUE;
for (int i = 1; i <= j; i++) {
answer = Math.max(answer, p[i] + r[j-i]);
}
r[j] = answer;
}
return r[n];
}
Step 3: compute the value of an optimal solution
public static int cut_bottom_up(int n) {
r[0] = 0;
for (int j = 1; j <= n; j++) {
int answer = Integer.MIN_VALUE;
for (int i = 1; i <= j; i++) {
answer = Math.max(answer, p[i] + r[j-i]);
}
r[j] = answer;
}
return r[n];
}
Step 4: construct an optimal solution from computed information
public static int cut_bottom_up(int n) {
r[0] = 0;
for (int j = 1; j <= n; j++) {
int answer = Integer.MIN_VALUE;
for (int i = 1; i <= j; i++) {
if (answer < p[i] + r[j-i]) {
answer = p[i] + r[j-i];
s[j] = i;
}
}
r[j] = answer;
}
return r[n];
}
Step 4: construct an optimal solution from computed information
\(i\)
\(p_i\)
1 2 3 4 5 6 7 8 9 10
1 5 8 9 10 17 17 20 24 30
\(s_i\)
1 2 3 2 2 6 1 2 3 10
Step 4: construct an optimal solution from computed information
\(i\)
\(p_i\)
1 2 3 4 5 6 7 8 9 10
1 5 8 9 10 17 17 20 24 30
\(s_i\)
1 2 3 2 2 6 1 2 3 10
public static String construct_solution(int n) {
String ans = "";
while (n > 0) {
ans = ans + s[n] + " ";
n = n - s[n];
}
return ans;
}
Step 4: construct an optimal solution from computed information
static int[] p = {0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30,
33, 36, 40, 42, 45, 50, 52, 58, 58, 60,
62, 65, 66, 72, 80, 82, 83, 85, 87, 88};
n | optimal answer | optimal cuts |
---|---|---|
10 | 30 | 10 |
15 | 45 | 2 and 13 |
20 | 63 | 2 and 18 |
25 | 80 | 25 |
30 | 94 | 12 and 18 |
final words
definition
brute force
brute force
brute force
brute force
brute force
Case A:
Case B:
optimal substructure
optimal substructure
A B C B D A B
B D C A B A
B C B D A B
B D C A B A
A B C B D A B
D C A B A
Case A:
optimal substructure
Proof (by contradiction):
Case A:
optimal substructure
B C B D A B
B D C A B A
C B D A B
B D C A B A
B C B D A B
D C A B A
Case B:
C B D A B
D C A B A
optimal substructure
Case B:
Proof (by contradiction):
recursive relation
\(x_1\ x_2\ x_3 \dots x_m\)
\(y_1\ y_2\ y_3\dots y_n\)
\(x_2\ x_3 \dots x_m\)
\(y_1\ y_2\ y_3\dots y_n\)
\(x_1\ x_2\ x_3 \dots x_m\)
\(y_2\ y_3\dots y_n\)
\(x_2\ x_3 \dots x_m\)
\(y_2\ y_3\dots y_n\)
\(x_1 = y_1\)
\(x_1 \ne y_1\)
\(x_1 \ne y_1\)
recursive relation
\[ \text{LCSS}(X,Y) = \begin{cases} 0 & \text{if \(X\) or \(Y\) is empty}\\1 + \text{LCSS}(X_2,Y_2) & \text{if $x_1 = y_1$}\\\max\left(\text{LCSS}(X_2,Y_1),\text{LCSS}(X_1,Y_2)\right) & \text{if $x_1 \ne y_2$}\end{cases} \]
\(x_1\ x_2\ x_3 \dots x_m\)
\(y_1\ y_2\ y_3\dots y_n\)
\(x_2\ x_3 \dots x_m\)
\(y_1\ y_2\ y_3\dots y_n\)
\(x_1\ x_2\ x_3 \dots x_m\)
\(y_2\ y_3\dots y_n\)
\(x_2\ x_3 \dots x_m\)
\(y_2\ y_3\dots y_n\)
\(x_1 = y_1\)
\(x_1 \ne y_1\)
\(x_1 \ne y_1\)
top-down solution
public static int lcss_top_down(String x, String y) {
int i = x.length()-1, j = y.length()-1;
if (x.length() == 0 || y.length() == 0)
return 0;
if (lcss[i][j] != -1)
return lcss[i][j];
else if (x.charAt(0) == y.charAt(0))
return lcss[i][j] = i=1 + lcss_top_down(x.substring(1),y.substring(1));
else
return lcss[i][j] = Math.max(lcss_top_down(x.substring(1),y), lcss_top_down(x,y.substring(1)));
}
bottom-up solution
for (int i = 0; i < n+1; i++) lcss[m][i] = 0;
for (int i = 0; i < m+1; i++) lcss[i][n] = 0;
for (int i = m-1; i > -1; i--){
for (int j = n-1; j > -1; j--){
// if character matches, then go diagonally
if(a.charAt(i) == b.charAt(j))
lcss[i][j] = 1 + lcss[i+1][j+1];
// else, compare the cell to your right and to your bottom,
// and pick the larger one
else
lcss[i][j] = Integer.max(lcss[i][j+1], lcss[i+1][j]);
}
}