1. Home
  2. Design & Analysis of Algorithms
  3. Longest Common Subsequence (LCS)

Longest Common Subsequence (LCS)

Find the longest sequence of letters that appears, in order, in two strings. A classic dynamic programming problem behind diff tools and DNA comparison.

Interactive 3DIntermediate12 min readDAAUpdated

Drag to rotate · Right-drag to pan · Click, then scroll to zoom · Space play · ←→ step

What's happening

Pseudocode

    Try this in the 3D model

    • Pause on a green "match" step. Which cell does the value come from?
    • Type your own two words and solve — e.g. your name and a friend's name.
    • Look at the 3D bars from the side. The heights only ever go up as you move right or down. Why?

    What is a subsequence?

    A subsequence keeps characters in their original order but may skip some. From "ABCDE":

    • "ACE" ✅ (skip B and D)
    • "AEC" ❌ (order changed)

    A substring is stricter — it must be contiguous ("BCD").

    The Longest Common Subsequence (LCS) of two strings is the longest subsequence that appears in both. For "ABCBDAB" and "BDCABA", one LCS is "BCBA" (length 4).

    Why dynamic programming?

    A string of length m has 2ᵐ subsequences — checking them all is hopeless. But the problem has optimal substructure: the LCS of two strings can be built from the LCS of their prefixes.

    Let dp[i][j] = length of the LCS of the first i letters of A and the first j letters of B.

    if A[i] == B[j]:   dp[i][j] = dp[i−1][j−1] + 1            (match: extend)
    else:              dp[i][j] = max(dp[i−1][j], dp[i][j−1]) (drop a letter from A or B)

    Base case: row 0 and column 0 are 0 (an empty string has nothing in common).

    In the 3D model, a match lights up the diagonal cell (green), and a mismatch compares the cells above and to the left (yellow). Bar heights equal the LCS lengths, so the table looks like a staircase rising towards the far corner.

    Reading the answer

    dp[m][n] is the length. To get the actual subsequence, start at the bottom-right cell and walk back:

    • if the letters match, that letter is part of the LCS — move diagonally;
    • otherwise move to whichever neighbour (up or left) holds the bigger value.

    Collect the matched letters in reverse.

    Code

    def lcs(A, B):
        m, n = len(A), len(B)
        dp = [[0] * (n + 1) for _ in range(m + 1)]
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if A[i - 1] == B[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
        # trace back
        i, j, out = m, n, []
        while i > 0 and j > 0:
            if A[i - 1] == B[j - 1]:
                out.append(A[i - 1]); i -= 1; j -= 1
            elif dp[i - 1][j] >= dp[i][j - 1]:
                i -= 1
            else:
                j -= 1
        return dp[m][n], "".join(reversed(out))
    
    print(lcs("ABCBDAB", "BDCABA"))   # (4, 'BCBA')
    print(lcs("AGGTAB", "GXTXAYB"))   # (4, 'GTAB')
    #include <iostream>
    #include <string>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    int lcsLength(const string& A, const string& B) {
        int m = A.size(), n = B.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
                dp[i][j] = (A[i - 1] == B[j - 1]) ? dp[i - 1][j - 1] + 1
                                                  : max(dp[i - 1][j], dp[i][j - 1]);
        return dp[m][n];
    }
    
    int main() {
        cout << lcsLength("ABCBDAB", "BDCABA") << "\n";   // 4
    }

    Where is LCS used?

    • diff and Git: the lines that stay the same form an LCS; everything else is shown as added or removed.
    • Bioinformatics: comparing DNA and protein sequences.
    • Plagiarism detection and spell-checking (closely related to edit distance).
    • Longest common substring (contiguous) — similar table, but reset to 0 on a mismatch.
    • Edit distance (Levenshtein) — minimum insertions, deletions and substitutions to turn A into B.
    • Longest increasing subsequence — a single-sequence cousin.

    Common mistakes

    • Confusing subsequence (gaps allowed) with substring (no gaps).
    • Indexing A[i] instead of A[i − 1] when the table has an extra row and column.
    • Expecting a unique answer — there can be several LCSs of the same length.

    Complexity at a glance

    Case / operationTimeWhy
    Fill the tableO(m × n)One cell per pair of prefixes.
    Trace backO(m + n)
    Brute force (all subsequences)O(2ᵐ × n)
    Extra spaceO(m × n)

    Quick check

    Test yourself — pick an answer to see if you got it.

    1. Which of these is a subsequence of "ALGORITHM"?

    2. If A[i] == B[j], then dp[i][j] equals…

    3. What is the LCS length of "ABCBDAB" and "BDCABA"?

    4. Which tool relies on the LCS idea?

    Saved only in this browser — no account needed.
    Spotted a mistake or a bug in the 3D model?

    Report a mistake

    in Longest Common Subsequence (LCS). Thank you — every report makes the lesson better for the next reader.

    We'll also include a link to the step of the 3D model you're on and your browser type, so we can reproduce it.