The Fibonacci sequence is defined by the recurrence: F_n = \begin{cases} 0 & \text{if } n=0\\ 1 & \text{if } n=1\\ F_{n-1} + F_{n-2} & \text{otherwise} \end{cases}
A direct recursive translation to compute the n^{\text{th}} Fibonacci number:
Let T(n) be the number of operations to compute F_n: T(n) = T(n-1) + T(n-2) + 1 T(0) = T(1) = 1
The solution to this recurrence is: T(n) = O(\phi^n) \quad \text{where } \phi = \frac{\sqrt{5}+1}{2} \approx 1.618
Instead of top-down recursion, we can compute solutions bottom-up and store them in an array:
function iterativeFib(n) {
let F = new Array(n + 1);
F[0] = 0;
F[1] = 1;
for (let i = 2; i <= n; i++) {
F[i] = F[i - 1] + F[i - 2];
}
return F[n];
}The Core Philosophy: Solve each subproblem exactly once and store the result to avoid redundant calculations—trading space for time.
Optimal Substructure: An optimal solution to the problem can be constructed efficiently from the optimal solutions of its subproblems.
Overlapping Subproblems: The recursive search space contains the same subproblems repeatedly, rather than generating new independent subproblems at each step.
Given a sequence X = \langle x_1, x_2, \ldots, x_m \rangle, another sequence Z = \langle z_1, z_2, \ldots, z_k \rangle is a subsequence of X if there exists a strictly increasing sequence \langle i_1, i_2, \ldots, i_k \rangle of indices of X such that for all j = 1, 2, \ldots, k, we have x_{i_j} = z_j.
Example: Z = \langle B, C, D, B \rangle is a subsequence of X = \langle A, B, C, B, D, A, B \rangle with corresponding index sequence \langle 2, 3, 5, 7 \rangle.
Given two sequences X and Y, we say that Z is a common subsequence of X and Y if Z is a subsequence of both X and Y.
Example: Z = \langle B, C, B, A \rangle is a common subsequence of X = \langle A, B, C, B, D, A, B \rangle and Y = \langle B, D, C, A, B, A \rangle.
Given two sequences X = \langle x_1, x_2, \ldots, x_m \rangle and Y = \langle y_1, y_2, \ldots, y_n \rangle, the goal is to find a maximum length common subsequence of X and Y.
Brute Force Method:
Optimal Substructure
Let Z = \langle z_1, \ldots, z_k \rangle be any LCS of X and Y.
If x_i = y_j, then \text{lcs}(i, j) = \text{lcs}(i-1, j-1) + 1.
If x_i \neq y_j, then \text{lcs}(i, j) = \max(\text{lcs}(i-1, j), \text{lcs}(i, j-1)).
\text{lcs}(i, j) = \begin{cases} 0 & \text{if } i = 0 \text{ or } j = 0 \\ \text{lcs}(i-1, j-1) + 1 & \text{if } x_i = y_j \\ \max(\text{lcs}(i-1, j), \text{lcs}(i, j-1)) & \text{if } x_i \neq y_j \end{cases}
The algorithms above compute only the length of the LCS. We save extra direction flags (hints) in a table b[1..m, 1..n] to recover the actual sequence.
Three flags maintained during the LCS algorithm:
We backtrack from the bottom-right corner b[m, n] of the table to the start (i=0 or j=0):
function printLCS(b, X, i, j) {
if (i === 0 || j === 0) return;
if (b[i][j] === "↖") {
printLCS(b, X, i - 1, j - 1);
print(X[i]); // Output character (part of LCS)
} else if (b[i][j] === "↑") {
printLCS(b, X, i - 1, j);
} else {
printLCS(b, X, i, j - 1);
}
}CS F364: Design & Analysis of AlgorithmsTulasimohan Molli