Longest Common Subsequence
MedPrompt
Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0.
A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.
- For example,
"ace"is a subsequence of"abcde".
A common subsequence of two strings is a subsequence that is common to both strings.
Example 1:
Input: text1 = "abcde", text2 = "ace"
Output: 3
Explanation: The longest common subsequence is "ace" and its length is 3.Example 2:
Input: text1 = "abc", text2 = "abc"
Output: 3
Explanation: The longest common subsequence is "abc" and its length is 3.Example 3:
Input: text1 = "abc", text2 = "def"
Output: 0
Explanation: There is no such common subsequence, so the result is 0.
Constraints:
1 <= text1.length, text2.length <= 1000text1andtext2consist of only lowercase English characters.
Approaches
4 approaches with complexity analysis and trade-offs.
This approach optimizes the brute-force recursion by using a memoization table (usually a 2D array) to store the results of already computed subproblems. By caching results, we avoid redundant calculations for the same pair of indices (i, j). This technique is a form of top-down dynamic programming.
Algorithm
- Use the same recursive structure as the brute-force approach.
- Create a 2D array
memo[m][n]to store the results of subproblems, initialized with a value like -1. - In the recursive function
solve(i, j), first check ifmemo[i][j]is already computed (i.e., not -1). If so, return the stored value. - If not computed, calculate the result using the same recursive logic.
- Before returning the result, store it in
memo[i][j]for future use.
Walkthrough
The recursive structure is the same as the brute-force approach, but we enhance it with a cache to store intermediate results.
- We introduce a 2D array,
memo, of sizem x n(wheremistext1.length()andnistext2.length()) to store the results ofsolve(i, j). - We initialize the
memotable with a special value (e.g., -1) to indicate that a subproblem has not been solved yet. - Inside the recursive function
solve(i, j), before any computation, we first check ifmemo[i][j]has already been computed. If it has, we return the stored value immediately, avoiding a costly re-computation. - If the result is not in the memo table, we compute it using the same recursive logic as before.
- After computing the result, we store it in
memo[i][j]before returning it.
By storing results, we ensure that each subproblem (i, j) is solved only once.
class Solution { public int longestCommonSubsequence(String text1, String text2) { int m = text1.length(); int n = text2.length(); int[][] memo = new int[m][n]; for (int[] row : memo) { java.util.Arrays.fill(row, -1); } return solve(text1, text2, 0, 0, memo); } private int solve(String text1, String text2, int i, int j, int[][] memo) { if (i == text1.length() || j == text2.length()) { return 0; } if (memo[i][j] != -1) { return memo[i][j]; } int result; if (text1.charAt(i) == text2.charAt(j)) { result = 1 + solve(text1, text2, i + 1, j + 1, memo); } else { int option1 = solve(text1, text2, i + 1, j, memo); int option2 = solve(text1, text2, i, j + 1, memo); result = Math.max(option1, option2); } memo[i][j] = result; return result; }}Complexity
Time
O(m * n). Each state `(i, j)` is computed only once. There are `m * n` possible states.
Space
O(m * n) for the memoization table, plus O(m+n) for the recursion stack. The dominant factor is the memoization table.
Trade-offs
Pros
Drastically more efficient than brute-force, with polynomial time complexity.
Retains the intuitive top-down recursive structure, making it easy to reason about.
Cons
Requires O(m*n) space for the memoization table, which can be significant.
For very deep recursion paths (not an issue with problem constraints), it could theoretically cause a stack overflow.
Solutions
Solution
public class Solution { public int LongestCommonSubsequence(string text1, string text2) { int m = text1.Length, n = text2.Length; int[, ] f = new int[m + 1, n + 1]; for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (text1[i - 1] == text2[j - 1]) { f[i, j] = f[i - 1, j - 1] + 1; } else { f[i, j] = Math.Max(f[i - 1, j], f[i, j - 1]); } } } return f[m, n]; }}Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.