Skip to content
Forge Learn/2D Dynamic Programming
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

2D Dynamic Programming: LCS & Knapsack

7 min read

You'll learn to

  • -Recognize when a problem needs two dimensions of "progress" instead of one
  • -Implement longest_common_subsequence(a, b) with a grid-shaped dp table
  • -Implement 0/1 knapsack(weights, values, capacity) and explain the include-vs-exclude recurrence

Every 1D DP problem so far tracked progress with a single number, how many steps climbed, how much of the target amount reached. Some problems need to track two independent kinds of progress at once, and for those, a single dp array is not enough. The state needs a grid, with one dimension for each kind of progress. Two of the most common examples are comparing two sequences, and choosing among items under a shared budget.

Longest Common Subsequence

The longest common subsequence (LCS) of two strings a and b is the longest sequence of characters that appears in both, in the same relative order, but not necessarily contiguously. Progress here has two independent axes, how far into a you have compared, and how far into b, so the state is naturally dp[i][j]: the length of the LCS of the first i characters of a and the first j characters of b. If the current characters match, they extend the best subsequence found one step back on both axes. If they do not match, the best answer just carries forward whichever of "drop this character of a" or "drop this character of b" was already better.

longest_common_subsequence(a, b). A grid indexed by (position in a, position in b).

0/1 Knapsack

0/1 knapsack asks: given items with weights and values, and a capacity limit, what is the maximum total value achievable without exceeding the capacity, using each item at most once? Progress here also has two axes, how many items have been considered so far, and how much capacity remains, so dp[i][cap] holds the best achievable value using only the first i items with capacity cap available. For each item, the choice is binary: leave it out entirely (carry forward dp[i-1][cap] unchanged), or include it if it fits (add its value to whatever was achievable with the remaining capacity before considering it, dp[i-1][cap - weight] + value), and take whichever of those two is larger.

knapsack(weights, values, capacity). 0/1: each item used at most once.
  • -Whenever a problem's "progress" naturally has two independent measurements (two string positions, or items-considered vs. capacity-used), the dp state is a 2D grid, not a 1D array.
  • -LCS's recurrence looks one step diagonally back (both indices decrease) on a match, and one step back on either axis (whichever is better) on a mismatch.
  • -Knapsack's recurrence always compares "skip this item" against "take this item," never both at once. That binary choice is exactly what "0/1" refers to in the name.
  • -Both tables are filled row by row (or item by item), left to right, since each cell only depends on cells already computed above or to the left.

A useful gut check when starting any new DP problem: count how many independent quantities describe "how far along" you are. One quantity means a 1D array. Two independent quantities mean a 2D grid, indexed exactly by those two.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

Ready to Build This?

Level 71: 2D Dynamic Programming asks you to implement longest_common_subsequence(a, b) and knapsack(weights, values, capacity) using exactly the grid-shaped dp tables built in this chapter.