2Brute2Best
🔥0

Longest Common Subsequence

Medium·LeetCode #1143Basic Anchor
LeetCode
30-Second Mental ModelDP — Strings (LCS: match → diagonal + 1, else max of the two skips)
Complexity:O(2^(N + M))→O(N · M)
Key Insight

Row i only reads row i − 1 and the cell just left of it in row i. So two rows are enough: one for the row above and one being filled. After each letter of text1, the new row becomes the row above. Put the shorter string along the row, so each row is as short as possible.

Core Validity Invariant

Filling row by row, from left to right, means each cell reads only finished cells, so dp[i][j] is correct the moment it is written.

01

Problem

You are given two strings text1 and text2. Find the length of the longest common subsequence: a string you can make from both strings by deleting some letters (or none) and keeping the rest in order. Return that length. If the strings share no letter, return 0.

Input

Two strings, text1 and text2.

Output

The length of the longest string that is a subsequence of both.

Example 1
Input: text1 = "abcde", text2 = "ace"
Output: 3

ace is in both strings: delete b and d from abcde. No longer string is shared, so the answer is 3.

Example 2
Input: text1 = "abc", text2 = "abc"
Output: 3

The whole string is shared.

Example 3
Input: text1 = "abc", text2 = "def"
Output: 0

The strings share no letters.

Constraints
  • • 1 ≤ text1.length, text2.length ≤ 1000
  • • Lowercase English letters only.
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    Does a common subsequence need the same position in both strings, or only the same order of letters?

  2. 2.

    If I look at the last letter of each string, what choices do I have for that pair?

  3. 3.

    Do I have to use the last letter of text1? Can I skip it and still have a valid answer?

  4. 4.

    If the last letters match, how does that change the question about the shorter strings?

  5. 5.

    What is the answer when one of the strings is empty?

03

Brute force approach

04

Better approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceCompare the last letters: a match is used; otherwise drop one and recurseO(2^(N + M))O(N + M)
Betterdp[i][j] for every pair of prefixes; each cell is filled once from finished cellsO(N · M)O(N · M)
OptimalKeep only the row above and the current row; same rule, less memoryO(N · M)O(min(N, M))
09

Why does this belong to this pattern?

Clues in the problem statement
  • “subsequence (letters can be skipped, order kept)”Gaps are allowed, so the answer is not a run of neighbours. That makes it LCS, not a substring.
  • “common to both strings”Two strings and one answer: compare prefixes of both at once with a table dp[i][j].
  • “return the length”We need a count, not the letters, so each cell stores one number.
Two strings+match → diagonal + 1+no match → max of the two skips→DP — Strings (LCS: match → diagonal + 1, else max of the two skips)

This question is the anchor of the sub-pattern. The rule is match → 1 + dp[i−1][j−1], and otherwise max(dp[i−1][j], dp[i][j−1]). The later questions in this sub-pattern reuse this table and change one thing. Watch the gap: a subsequence can skip letters, so a mismatch never resets the count to 0, unlike a substring.

10

Don't make this mistake

Pattern confusion: the common substring rule
✗ if (text1[i-1] == text2[j-1]) cur[j] = prev[j-1] + 1; else cur[j] = 0; // reset: a substring must be one unbroken run
✓ if (text1[i-1] == text2[j-1]) cur[j] = prev[j-1] + 1; else cur[j] = max(prev[j], cur[j-1]);

The substring rule resets to 0 on a mismatch, because a substring must be one unbroken run. A subsequence can skip letters. On the dry run (abcde and ace), the wrong rule returns 1, but the answer is 3. The letters a, c and e are never next to each other in abcde, so the run resets every time.

One row, updated left to right
✗ for (int j = 1; j <= m; j++) row[j] = (x == text2[j-1]) ? row[j-1] + 1 : max(row[j], row[j-1]);
✓ cur[j] = (x == text2[j-1]) ? prev[j-1] + 1 : max(prev[j], cur[j-1]); // prev = the old row

In the wrong version, row[j−1] has already been overwritten with this letter's value, so one letter of text1 can be used twice. With text1 = "a" and text2 = "aa", it returns 2, but the answer is 1, because text1 has only one a. Keep the old row in prev and write into cur.

Taking only the diagonal on a mismatch
✗ else cur[j] = prev[j-1]; // only the diagonal
✓ else cur[j] = max(prev[j], cur[j-1]);

When the letters differ, the answer can come from the cell above or the cell to the left, not just the diagonal. On the dry run, using only the diagonal on a mismatch returns 1, but the answer is 3.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like DP — Strings translate into production architectures and low-level memory mechanics.