Foundations

Longest Common Subsequence

Find the length of the longest common subsequence of two strings - 2D DP where matching characters extend the diagonal, non-matching cells take the max of left and above.

mediumFree~15 min

Problem

Code review tools and plagiarism detectors measure similarity between two sequences by finding their longest common subsequence. Given two strings, return the length of their longest common subsequence - the longest sequence of characters that appears in both strings in the same relative order (not necessarily contiguous).

Example 1:

Input: text1="corvette", text2="covert"
Output: 5
Explanation: The longest common subsequence is "covet".

Example 2:

Input: text1="kitten", text2="kitten"
Output: 6
Explanation: The full strings match.

Example 3:

Input: text1="plan", text2="nlp"
Output: 1
Explanation: The strings share only single common characters; the best is "p" (or "l" or "n").

Constraints:

  • 0 <= text1.length, text2.length <= 500
  • text1 and text2 consist of lowercase English letters

Verification

Trace 'kitten' vs 'kitten': dp[1][1]=1 (k==k), dp[1][2]=1, ... dp[1][6]=1. dp[2][1]=1, dp[2][2]=2 (i==i), ... dp[2][6]=2. Each row adds exactly one match, so dp[6][6]=6 (t==t). Return 6.

Solution Breakdown

Approach: 2D tabulation over prefixes of both strings (sequence alignment).

Define dp[i][j] as the LCS length of the first i characters of text1 and the first j of text2. The grid has an extra row 0 and column 0 of zeros: an empty prefix shares nothing, so those are the base cases. Filling proceeds by the last characters of each prefix. If text1[i-1] == text2[j-1], that shared character must extend whatever was optimal before both of them, so dp[i][j] = dp[i-1][j-1] + 1 - the diagonal plus one. If they differ, the LCS cannot use both, so the best is to drop one character from one string: dp[i][j] = max(dp[i-1][j], dp[i][j-1]), the better of skipping text1's last char or text2's last char. Rows fill top to bottom and left to right, so every neighbor a cell reads (above, left, diagonal) is already final. The answer accumulates in the bottom-right cell dp[rows-1][cols-1]. The i-1/j-1 offset is the recurring trap: the table is indexed one ahead of the strings because of the zero padding. Trace kitten vs kitten: the diagonal increments at every match, giving 1, 2, 3, 4, 5, 6.

Edge cases: An empty input string makes either rows or cols equal to 1, so the loops never run and dp[rows-1][cols-1] stays 0 - the correct empty-overlap answer. Strings with no common characters never hit the match branch and return 0.

Complexity: O(m x n) time, O(m x n) space - one cell per prefix pair, each computed in O(1); reducible to two rows if only the length is needed.

Done reading? Mark it so it sticks in your dashboard.

Discussion