Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 49

Solving 2D Dynamic Programming Problems

Recommended resources

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:

  1. Define the State: What does dp[i][j] represent?
  2. 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])?
  3. Identify Base Cases: What are the simplest possible subproblems, usually involving empty inputs?
  4. 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 prefix text1[0...i-1] (length i) and text2[0...j-1] (length j).

Now, to compute dp[i][j], we look at the last characters of our current prefixes: text1[i-1] and text2[j-1].

  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] and text2[0...j-2].

  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 of text1[0...i-2] and text2[0...j-1]. This is dp[i-1][j].
    • Exclude the character text2[j-1] and find the LCS of text1[0...i-1] and text2[0...j-2]. This is dp[i][j-1].
      We take the best (longest) of these two options.

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.

  1. Initialization: Create a (m+1) x (n+1) DP table, where m is the length of text1 and n is the length of text2. Initialize it with zeros.
  2. 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 is 0. Our initialization already handles this.
  3. Iteration: Use nested loops to iterate from i=1 to m and j=1 to n. Inside the loops, apply the recurrence relation we just defined.
  4. 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:

  1. Insert a character.
  2. Delete a character.
  3. Replace a character.

Example:

  • word1 = "horse"
  • word2 = "ros"
  • Operations:
    1. Replace 'h' with 'r' (rorse)
    2. Delete 'r' (rose)
    3. Delete 'e' (ros)
  • 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 convert word1[0...i-1] to word2[0...j-1].

Again, we compare word1[i-1] and word2[j-1]:

  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.

  2. 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] match word2[0...j-1]. This is equivalent to matching word1[0...i-1] to word2[0...j-2] and then inserting the character word2[j-1]. The cost is 1 + dp[i][j-1].
    • Delete: We can match word1[0...i-2] to word2[0...j-1] and then delete the character word1[i-1]. The cost is 1 + dp[i-1][j].
    • Replace: We can match word1[0...i-2] to word2[0...j-2] and then replace word1[i-1] with word2[j-1]. The cost is 1 + dp[i-1][j-1].
      We take the minimum of these three choices.

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.

Edit Distance (Levenshtein Distance) Algorithm Implementation and Visualization
CAPTION: This image visually connects the Python code for Edit Distance with the DP table it generates. The code implements the recurrence relation, while the table shows the calculated minimum costs for each subproblem, leading to the final answer.

Bottom-Up Implementation

The implementation is very similar to LCS, but with different base cases and a different recurrence for the non-matching case.

  1. Initialization: Create a (m+1) x (n+1) DP table.
  2. Base Cases:
    • dp[0][j] = j: To convert an empty string to a string of length j, you need j insertions.
    • dp[i][0] = i: To convert a string of length i to an empty string, you need i deletions.
  3. Iteration: Use nested loops from i=1 to m and j=1 to n, applying the Edit Distance recurrence.
  4. 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 m and n, the state is typically dp[i][j] and the solution is built in an O(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])
  • 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])
  • 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 (i and j).

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.