Hello! Welcome to the next lesson in our dynamic programming module.
In our last session, we mastered the 1D DP pattern, solving problems like Climbing Stairs and House Robber by defining a state dp[i] and finding a recurrence relation. Today, we're leveling up by extending this core idea to two dimensions.
This lesson addresses a crucial category of interview problems that involve comparing two sequences (like strings or arrays). You'll learn how to solve 2D DP problems by defining a state dp[i][j], which typically represents the solution to a subproblem concerning the first i elements of one input and the first j elements of another.
Our focus will be on two foundational problems that are part of the same high-ROI interview pattern: Longest Common Subsequence (LCS) and Edit Distance. Mastering these will equip you with a powerful template for a whole family of related questions.
From 1D to 2D Dynamic Programming
The thought process remains the same as for 1D DP:
- Define the State: What does
dp[i][j]represent? - Find the Recurrence Relation: How can we compute
dp[i][j]from previously solved subproblems (e.g.,dp[i-1][j],dp[i][j-1],dp[i-1][j-1])? - Identify Base Cases: What are the simplest possible subproblems, usually involving empty inputs?
- Implement Bottom-Up: Build a 2D table (or grid) to store results, starting from the base cases.
Let's dive into our first problem.
1. Longest Common Subsequence (LCS)
The LCS problem is a classic that forms the basis for many other DP problems, including sequence alignment in bioinformatics, which might resonate with your AI interests.
Problem: Given two strings, text1 and text2, find the length of their longest common subsequence. A subsequence is a new string generated from the original string with zero or more characters deleted, without changing the relative order of the remaining characters.
Example:
text1 = "abcde"text2 = "ace"- The longest common subsequence is
"ace", and its length is 3.
Core Logic and Recurrence Relation
Let's define our state:
dp[i][j]will be the length of the LCS between the prefixtext1[0...i-1](lengthi) andtext2[0...j-1](lengthj).
Now, to compute dp[i][j], we look at the last characters of our current prefixes: text1[i-1] and text2[j-1].
-
Characters Match: If
text1[i-1] == text2[j-1], this matching character is part of our LCS. We can add 1 to the length of the LCS of the remaining prefixes,text1[0...i-2]andtext2[0...j-2]. -
Characters Don't Match: If
text1[i-1] != text2[j-1], the LCS of the current prefixes must be the same as the LCS of a smaller subproblem. We have two choices:- Exclude the character
text1[i-1]and find the LCS oftext1[0...i-2]andtext2[0...j-1]. This isdp[i-1][j]. - Exclude the character
text2[j-1]and find the LCS oftext1[0...i-1]andtext2[0...j-2]. This isdp[i][j-1].
We take the best (longest) of these two options.
- Exclude the character
The following video provides an excellent visual explanation of this logic using a 2D grid.
Longest Common Subsequence - Dynamic Programming - Leetcode 1143
This video from NeetCode will walk you through the entire thought process for LCS, from understanding the subproblems to building the bottom-up DP solution.
Watch the video from the beginning to 18:12. Pay close attention to: 00:00 - 04:23: The definition of a subsequence and the core idea of breaking the problem down based on whether the last characters match. 04:23 - 13:58: How the 2D DP grid is used to represent subproblems and how to fill it using the recurrence relation. This is the most crucial part. 13:58 - 18:12: The bottom-up Python implementation, showing how the logic translates directly into code.
Bottom-Up Implementation
As shown in the video, we implement this using a 2D array, often called a DP table.
- Initialization: Create a
(m+1) x (n+1)DP table, wheremis the length oftext1andnis the length oftext2. Initialize it with zeros. - Base Cases: The first row and column of the grid will remain
0. This is because the LCS of any string with an empty string is0. Our initialization already handles this. - Iteration: Use nested loops to iterate from
i=1tomandj=1ton. Inside the loops, apply the recurrence relation we just defined. - Result: The final answer is stored in the bottom-right cell of the table,
dp[m][n].
The time and space complexity are both O(m * n) because we fill every cell of the m x n grid once.
Test your understanding!
Manually fill out the DP table to find the length of the LCS for text1 = "AGB" and text2 = "GAB".
Show answer
The DP table would be 4x4. Let text1 be on the vertical axis and text2 on the horizontal.
| "" | G | A | B | |
|---|---|---|---|---|
| "" | 0 | 0 | 0 | 0 |
| A | 0 | 0 | 1 | 1 |
| G | 0 | 1 | 1 | 1 |
| B | 0 | 1 | 1 | 2 |
dp[1][1](A vs G): No match.max(dp[0][1], dp[1][0]) = max(0,0) = 0.dp[1][2](A vs A): Match!1 + dp[0][1] = 1.dp[2][1](G vs G): Match!1 + dp[1][0] = 1.dp[3][3](B vs B): Match!1 + dp[2][2] = 1 + 1 = 2.
The final result is dp[3][3] = 2. The LCS is "AB".
2. Edit Distance
Edit Distance (also known as Levenshtein Distance) is a classic variation of the LCS pattern. It's a common interview question and has practical applications in areas like spell checkers and computational biology.
Problem: Given two strings, word1 and word2, find the minimum number of operations required to convert word1 to word2. The allowed operations are:
- Insert a character.
- Delete a character.
- Replace a character.
Example:
word1 = "horse"word2 = "ros"- Operations:
- Replace 'h' with 'r' (
rorse) - Delete 'r' (
rose) - Delete 'e' (
ros)
- Replace 'h' with 'r' (
- Minimum operations: 3.
Core Logic and Recurrence Relation
This problem fits the same 2D DP template.
- State:
dp[i][j]will be the minimum number of operations to convertword1[0...i-1]toword2[0...j-1].
Again, we compare word1[i-1] and word2[j-1]:
-
Characters Match: If
word1[i-1] == word2[j-1], no operation is needed for this character. The cost is simply the cost of converting the prefixes without these characters. -
Characters Don't Match: If
word1[i-1] != word2[j-1], we must perform one operation. We choose the one that results in the minimum total cost:- Insert: We want to make
word1[0...i-1]matchword2[0...j-1]. This is equivalent to matchingword1[0...i-1]toword2[0...j-2]and then inserting the characterword2[j-1]. The cost is1 + dp[i][j-1]. - Delete: We can match
word1[0...i-2]toword2[0...j-1]and then delete the characterword1[i-1]. The cost is1 + dp[i-1][j]. - Replace: We can match
word1[0...i-2]toword2[0...j-2]and then replaceword1[i-1]withword2[j-1]. The cost is1 + dp[i-1][j-1].
We take the minimum of these three choices.
- Insert: We want to make
The following video and article explain this logic in detail.
Edit Distance - Dynamic Programming - Leetcode 72 - Python
The NeetCode video on Edit Distance clearly explains the three choices (insert, delete, replace) and how they map to different cells in the DP grid.
Watch this entire video (approx. 21 mins). It's a bit longer but covers everything you need. Focus on: 01:28 - 04:24: The base cases, which are crucial for this problem. 04:24 - 10:32: The core recursive logic explaining how insert, delete, and replace operations correspond to moving to different subproblems (and thus different cells in the grid). 10:32 - 20:56: The bottom-up DP formulation and implementation.
To reinforce this, the following article provides a superb, step-by-step walkthrough of filling the DP table.
72. Edit Distance - In-Depth Explanation
This AlgoMonster article gives a detailed textual breakdown of the solution, which is great for review. The example walkthrough is especially clear.
Read the sections 'Intuition', 'Solution Approach', and 'Example Walkthrough'. The walkthrough for converting 'cat' to 'cut' will solidify your understanding of how the table is filled cell by cell.

Bottom-Up Implementation
The implementation is very similar to LCS, but with different base cases and a different recurrence for the non-matching case.
- Initialization: Create a
(m+1) x (n+1)DP table. - Base Cases:
dp[0][j] = j: To convert an empty string to a string of lengthj, you needjinsertions.dp[i][0] = i: To convert a string of lengthito an empty string, you needideletions.
- Iteration: Use nested loops from
i=1tomandj=1ton, applying the Edit Distance recurrence. - Result: The answer is
dp[m][n].
Like LCS, the time and space complexity are O(m * n).
Conclusion
In this lesson, we transitioned from 1D to 2D dynamic programming and solved two cornerstone problems in this category. You've seen how a single pattern can be adapted to solve different, but related, problems.
Key Takeaways:
- 2D DP Pattern: For problems comparing two sequences of length
mandn, the state is typicallydp[i][j]and the solution is built in anO(m * n)table. - LCS Recurrence:
- If
s1[i-1] == s2[j-1]:dp[i][j] = 1 + dp[i-1][j-1] - If
s1[i-1] != s2[j-1]:dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- If
- Edit Distance Recurrence:
- If
word1[i-1] == word2[j-1]:dp[i][j] = dp[i-1][j-1] - If
word1[i-1] != word2[j-1]:dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
- If
- Base Cases are Key: Pay close attention to the base cases. For LCS, it was all zeros. For Edit Distance, it was the cost of insertions/deletions against an empty string (
iandj).
In our next lesson, we will continue our exploration of dynamic programming by tackling knapsack-style problems. These problems introduce a new constraint (like weight or capacity) and will require a slightly different, but equally powerful, way of defining our DP state.