Dynamic ProgrammingPrint Longest Common Subsequence (LCS)

Print Longest Common Subsequence (LCS)

18 mins read The Jat Hard Updated 11 months ago
Dynamic Programming

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
Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *

Your experience on this site will be improved by allowing cookies Cookie Policy