3.7.10Algorithm Paradigms

DP problems — Longest Common Subsequence (LCS)

1,858 words8 min readdifficulty · medium

WHAT is LCS (vs. substring)

WHY care? LCS powers diff tools, version control merges, DNA/protein alignment, and plagiarism detection. It is the canonical 2-D dynamic-programming problem.


HOW to derive the recurrence (from scratch)

Let the two strings be X=x1x2xmX = x_1 x_2 \dots x_m and Y=y1y2ynY = y_1 y_2 \dots y_n.

Define L(i,j)=length of LCS of the prefixes X[1..i] and Y[1..j].L(i, j) = \text{length of LCS of the prefixes } X[1..i] \text{ and } Y[1..j].

We want L(m,n)L(m, n).

Think about the LAST characters xix_i and yjy_j. Either they match or they don't.

Case 1 — they match (xi=yjx_i = y_j). This shared character can safely end the LCS. Why? Because if an optimal LCS of these prefixes does NOT use this matching pair, we can always swap in this pair without losing length (steel-manned below). So pair them up and recurse on the smaller prefixes: L(i,j)=1+L(i1,j1).L(i,j) = 1 + L(i-1, j-1).

Case 2 — they don't match (xiyjx_i \ne y_j). The LCS cannot use BOTH xix_i and yjy_j as its final character (they differ). So at least one of them is useless as an endpoint — drop one and take the better option: L(i,j)=max(L(i1,j),  L(i,j1)).L(i,j) = \max\big(L(i-1, j),\; L(i, j-1)\big).

Base case. An empty prefix has no common subsequence: L(0,j)=L(i,0)=0.L(0, j) = L(i, 0) = 0.


The DP table

We fill a (m+1)×(n+1)(m+1)\times(n+1) table row by row. Each cell looks up-left (diagonal) when characters match, otherwise up and left.

Figure — DP problems — Longest Common Subsequence (LCS)

Worked Example 1 — AGGTAB vs GXTXAYB


Worked Example 2 — recovering the actual string (traceback)


Small dry run you should do mentally

Recall Forecast-then-verify

Before reading: predict LCS length of ABC and AC. Hidden answer: characters in order A…C → length 2 (AC). Table corner = 2. ✅ Did your forecast match?


Common mistakes (steel-manned)


Reference implementation

def lcs(X, Y):
    m, n = len(X), len(Y)
    L = [[0]*(n+1) for _ in range(m+1)]
    for i in range(1, m+1):
        for j in range(1, n+1):
            if X[i-1] == Y[j-1]:
                L[i][j] = 1 + L[i-1][j-1]   # match: diagonal + 1
            else:
                L[i][j] = max(L[i-1][j], L[i][j-1])
    return L[m][n]

Recall Explain to a 12-year-old (Feynman)

Imagine two friends each wrote down a list of cartoons they watched, in order. You want the longest playlist of cartoons BOTH watched in the same order — you can skip cartoons, but you can't reshuffle them. You compare from the end: if the last cartoon is the same on both lists, keep it and look at the shorter lists. If not, throw away the last one from whichever list and keep checking. The biggest matching playlist you can build is the LCS.


Active recall

What is a subsequence (vs substring)?
Characters kept in order but not necessarily contiguous; substring must be contiguous.
LCS recurrence when characters match?
L(i,j)=1+L(i1,j1)L(i,j)=1+L(i-1,j-1) (diagonal +1).
LCS recurrence when characters differ?
L(i,j)=max(L(i1,j),L(i,j1))L(i,j)=\max(L(i-1,j),\,L(i,j-1)).
Base case of LCS DP?
L(0,j)=L(i,0)=0L(0,j)=L(i,0)=0 (empty prefix).
Time and space complexity of standard LCS DP?
O(mn)O(mn) time, O(mn)O(mn) space (optimizable to O(min(m,n))O(\min(m,n))).
Where is the final answer located in the table?
Bottom-right cell L(m,n)L(m,n).
How do you recover the actual LCS string?
Traceback from L(m,n)L(m,n): diagonal on match (prepend char), else move toward larger of up/left.
Why +1 only on a match?
A new shared character can only be added when both strings supply the same character to extend the sequence.
LCS of AGGTAB and GXTXAYB?
Length 4, e.g. GTAB.

Connections

  • Dynamic Programming — LCS is the textbook 2-D table DP.
  • Edit Distance — same grid; insert/delete/replace costs generalize LCS.
  • Longest Common Substring — contiguous variant; resets on mismatch.
  • Longest Increasing Subsequence — LIS reduces to LCS of array and its sorted-unique copy.
  • Diff Algorithmgit diff is LCS-based.
  • Sequence Alignment — Needleman–Wunsch bioinformatics version.

Concept Map

defined via

contrast with

powers

formalized as

split on last chars

split on last chars

gives

gives

needs

fills

fills

initializes

read at corner

LCS longest common subsequence

Subsequence keeps order not contiguous

Substring contiguous

diff, VCS, DNA alignment

L i,j = LCS of prefixes

Case xi = yj

Case xi != yj

1 + L i-1,j-1

max of up and left

Base L 0,j = L i,0 = 0

m+1 x n+1 DP table

Answer L m,n, O mn time

Substring contiguous

Hinglish (regional understanding)

Intuition Hinglish mein samjho

LCS ka matlab hai do strings me sabse lambi aisi sequence jo dono me same order me aaye, par zaroori nahi ki characters lagatar (contiguous) hon. Jaise ABCBDAB aur BDCAB me BCAB common hai — beech me kuch letters skip kar sakte ho, bas order todna mana hai. Yahi cheez substring se alag hai, kyunki substring me letters lagatar hone chahiye.

Iska core idea bahut simple hai: dono strings ke last character ko dekho. Agar dono same hain, to woh character LCS ka part ban sakta hai, to 1 + (dono ko ek-ek chhota karke jo answer mile) — yani diagonal wali value plus 1. Agar last characters alag hain, to dono ko ek saath end nahi rakh sakte, to ek ko hata do aur max(upar wali, baayi wali) cell le lo. Base case: koi string khali hai to LCS = 0.

Isi recurrence ko hum ek (m+1)×(n+1)(m+1)\times(n+1) table me bharte hain, upar se neeche, left se right. Final answer hamesha bottom-right cell me milta hai. Agar actual string chahiye to corner se ulta chalo (traceback): match pe diagonal jao aur character pakdo, warna jidhar badi value hai udhar jao. Time O(mn)O(mn), space O(mn)O(mn).

Yaad rakhne ka mantra: "Match goes diagonal, miss takes the max". Yeh paradigm interview me bahut aata hai (diff tools, DNA alignment, git), aur isi ka chhota variation Edit Distance hai — to ek baar samajh liya to bahut saare problems unlock ho jaate hain.

Go deeper — visual, from zero

Test yourself — Algorithm Paradigms

Connections