Problem Statement
Given two strings text1
and text2
, return any one Longest Common Subsequence (LCS) between them.
A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.
If multiple LCS solutions exist, return any one of them.
Constraints
1 <= text1.length, text2.length <= 1000
text1 and text2 consist only of lowercase english letters
Examples
Example 1:
Input: text1 = "abcde"
text2 = "ace"
Output: "ace"
Explanation: "ace" is the longest common subsequence.
Example 2:
Input: text1 = "aggtab"
text2 = "gxtxayb"
Output: "gtab"
Explanation: "gtab" is one valid LCS. Other valid outputs (if same length) would also be accepted.
Example 3:
Input:
text1 = "abc"
text2 = "def"
Output:
""
Explanation:
There is no common subsequence. Return an empty string.
Different Approaches
1️⃣ Recursive Approach
Code:
#include <iostream>
#include <string>
using namespace std;
string lcsRecursive(string text1, string text2, int i, int j) {
// Base case: if either string is fully traversed
if (i == text1.length() || j == text2.length()) {
return "";
}
// If characters match, include this character and move diagonally
if (text1[i] == text2[j]) {
return text1[i] + lcsRecursive(text1, text2, i + 1, j + 1);
} else {
// Else, explore both options: skip from text1 or skip from text2
string option1 = lcsRecursive(text1, text2, i + 1, j);
string option2 = lcsRecursive(text1, text2, i, j + 1);
// Return the longer of the two options
return (option1.length() > option2.length()) ? option1 : option2;
}
}
string printLongestCommonSubsequence(string text1, string text2) {
return lcsRecursive(text1, text2, 0, 0);
}
int main() {
string s1 = "abcde";
string s2 = "ace";
string res = printLongestCommonSubsequence(s1, s2);
cout << res << endl; // Output: ace
return 0;
}
Complexity Analysis:
- Time Complexity:
O(2 ^ (min(s1.length()), s2.length()))
- Space Complexity:
O(min(s1.length(), s2.length())
2️⃣ Memoization
Code:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
string lcsMemo(string& text1, string& text2, int i, int j, vector<vector<string>>& dp) {
// Base case
if (i == text1.length() || j == text2.length()) {
return "";
}
// If already computed, return from memo table
if (!dp[i][j].empty()) {
return dp[i][j];
}
// If characters match, include it and move diagonally
if (text1[i] == text2[j]) {
dp[i][j] = text1[i] + lcsMemo(text1, text2, i + 1, j + 1, dp);
} else {
// Explore both options and take longer one
string option1 = lcsMemo(text1, text2, i + 1, j, dp);
string option2 = lcsMemo(text1, text2, i, j + 1, dp);
dp[i][j] = (option1.length() > option2.length()) ? option1 : option2;
}
return dp[i][j];
}
string printLongestCommonSubsequence(string text1, string text2) {
int l1 = text1.length();
int l2 = text2.length();
// Initialize memoization table with empty strings
vector<vector<string>> dp(l1, vector<string>(l2, ""));
return lcsMemo(text1, text2, 0, 0, dp);
}
int main() {
string s1 = "abcde";
string s2 = "ace";
string res = printLongestCommonSubsequence(s1, s2);
cout << res << endl; // Output: ace
return 0;
}
Complexity Analysis:
- Time Complexity:
O(l1 * l2 * min(l1, l2))
- Space Complexity:
O(l1 * l2 * min(l1, l2))
3️⃣ Tabulation
Code:
string printLongestCommonSubsequence(string text1, string text2) {
// Step 1: Get lengths of the input strings
int l1 = text1.length();
int l2 = text2.length();
// Step 2: Initialize a 2D dp table where dp[i][j] holds the LCS of
// text1[0...i-1] and text2[0...j-1]. Initialize all values to empty strings.
vector<vector<string>> dp(l1 + 1, vector<string>(l2 + 1, ""));
// Step 3: Fill the dp table using bottom-up dynamic programming
for (int i = 1; i <= l1; i++) {
for (int j = 1; j <= l2; j++) {
// Case 1: Characters match, so include this character in the LCS
if (text1[i - 1] == text2[j - 1]) {
// Add the current matching character to the LCS of previous indices
dp[i][j] = dp[i - 1][j - 1] + text1[i - 1]; // Correct order: left-to-right
}
// Case 2: Characters don't match, take the longer LCS from previous subproblems
else {
string option1 = dp[i - 1][j]; // Skip character from text1
string option2 = dp[i][j - 1]; // Skip character from text2
// Choose the option that gives a longer LCS
dp[i][j] = (option1.length() > option2.length()) ? option1 : option2;
}
}
}
// Step 4: The bottom-right cell contains the final LCS string
return dp[l1][l2];
}
Complexity Analysis:
- Time Complexity:
O(l1 * l2 * min(l1, l2))
- Space Complexity:
O(l1 * l2 * min(l1, l2))
- No recursion stack