Chapter 10. Dynamic Programming
Classical bottom-up table fills for weighted interval scheduling, longest increasing subsequence, optimal BSTs, and 0/1 knapsack.
This page: Classical forms. View LLP forms »
This page collects the classical / sequential implementations of the algorithms developed in this chapter. The lattice-linear (LLP) reformulations and chapter setup live on the LLP companion page.
WeightedIntervalScheduling
Classical sequential DP: pre-compute $p[\cdot]$, then sweep left to right, taking the better of "skip $j$" ($Opt(j-1)$) and "take $j$" ($w_j + Opt(p(j))$). $O(n)$ given $p$.
Time complexity: $O(n)$ once $p$ is in hand, $O(n \log n)$ to compute $p$.
int[] schedule(int[] s, int[] f, int[] w, int[] p) {
int n = s.length;
int[] opt = new int[n];
int[] G = new int[n];
opt[0] = 0;
int cur = 1;
while (cur < n) {
opt[cur] = opt[cur - 1];
if (w[cur] + opt[p[cur]] >= opt[cur - 1]) {
opt[cur] = w[cur] + opt[p[cur]];
G[cur] = 1;
} else {
G[cur] = 0;
};
cur = cur + 1;
};
return G;
}
LIS
The classical $O(n^2)$ DP: $dp[i] = 1$ initially, then for each $i$, scan all $j < i$ and update $dp[i] := \max(dp[i],\ dp[j] + 1)$ whenever $A[j] < A[i]$.
Time complexity: $O(n^2)$, where $n$ is the size of the array.
int[] solve(int[] A) {
int n = A.length;
int[] dp = new int[n];
int i = 0;
while (i < n) { dp[i] = 1; i = i + 1; };
i = 1;
while (i < n) {
int j = 0;
while (j < i) {
if (A[j] < A[i]) {
if (dp[j] + 1 > dp[i]) { dp[i] = dp[j] + 1; }
};
j = j + 1;
};
i = i + 1;
};
return dp;
}
OptimalBinarySearchTree
The classical $O(n^3)$ DP: fill $dp[i][j]$ by increasing range length, trying every key $r \in [i, j]$ as the root and combining left/right subtree costs.
Time complexity: $O(n^3)$, where $n$ is the number of keys.
double[][] solve(double[] prob) {
int n = prob.length;
double[][] dp = new double[n][n];
double[][] s = new double[n][n];
int i = 0;
while (i < n) {
dp[i][i] = prob[i];
s[i][i] = prob[i];
i = i + 1;
};
int len = 1;
while (len < n) {
int lo = 0;
while (lo < n - len) {
int hi = lo + len;
s[lo][hi] = s[lo][hi - 1] + prob[hi];
double best = infinity;
int r = lo;
while (r <= hi) {
double left = 0.0;
double right = 0.0;
if (r > lo) { left = dp[lo][r - 1]; };
if (r < hi) { right = dp[r + 1][hi]; };
double cost = s[lo][hi] + left + right;
if (cost < best) { best = cost; };
r = r + 1;
};
dp[lo][hi] = best;
lo = lo + 1;
};
len = len + 1;
};
return dp;
}
Knapsack01
The classical $O(nW)$ table fill. $G[i][c]$ is the optimum value using items $1..i$ within capacity $c$; row $i$ depends only on row $i-1$.
Time complexity: $O(nW)$, where $n$ is the number of items and $W$ is the capacity.
int[][] solve(int[] w, int[] v, int W) {
int n = w.length - 1;
int[][] G = new int[n + 1][W + 1];
int c = 0;
while (c <= W) {
G[0][c] = 0;
c = c + 1;
};
int i = 0;
while (i <= n) {
G[i][0] = 0;
i = i + 1;
};
i = 1;
while (i <= n) {
c = 1;
while (c <= W) {
if (w[i] > c) {
G[i][c] = G[i - 1][c];
} else {
int skip = G[i - 1][c];
int take = G[i - 1][c - w[i]] + v[i];
if (take > skip) {
G[i][c] = take;
} else {
G[i][c] = skip;
}
};
c = c + 1;
};
i = i + 1;
};
return G;
}