The problem. Given two strings text1 and text2, return the length of their longest common subsequence — the longest string you can get from both by deleting letters without reordering the rest. If they share no letters, return 0.
text1 = "abcde", text2 = "ace" -> 3 ("ace")
text1 = "abc", text2 = "abc" -> 3 ("abc")
text1 = "abc", text2 = "def" -> 0With two sequences, the state becomes a pair of prefixes. As in every DP, answer four questions. State — what does a cell mean? Here dp[i][j] is the answer for the first i letters of one string and the first j of the other. Transition — how does a cell follow from smaller ones? Usually by looking at the last letters of the two prefixes. Base case — an empty prefix: row 0 and column 0. Answer — the cell for both whole strings, dp[n][m]. Two strings give an (n + 1) × (m + 1) table and O(n · m) time; one string cut into pieces gives a single row, dp[i] for the first i letters.
Take the first i letters of text1 and the first j of text2. If their last letters are equal, the LCS can end with that letter: use it, and add one to the answer for both prefixes without it, dp[i-1][j-1]. If they differ, at least one of the two letters is not in the LCS — so drop one or the other and keep the better answer, max(dp[i-1][j], dp[i][j-1]).
function longestCommonSubsequence(text1, text2) { const lcs = (i, j) => { // LCS of text1[i..] and text2[j..] if (i === text1.length || j === text2.length) return 0; if (text1[i] === text2[j]) return 1 + lcs(i + 1, j + 1); return Math.max(lcs(i + 1, j), lcs(i, j + 1)); // skip a letter of one string or the other }; return lcs(0, 0);}Time O(2ⁿ⁺ᵐ), space O(n + m) for the call stack. Every mismatch branches in two, and the same (i, j) pairs are solved over and over. But there are only (n + 1) × (m + 1) different pairs, so remember each answer.
function longestCommonSubsequence(text1, text2) { const memo = new Map(); // "i,j" -> LCS of text1[i..] and text2[j..] const lcs = (i, j) => { if (i === text1.length || j === text2.length) return 0; const key = i + "," + j; if (memo.has(key)) return memo.get(key); const best = text1[i] === text2[j] ? 1 + lcs(i + 1, j + 1) : Math.max(lcs(i + 1, j), lcs(i, j + 1)); memo.set(key, best); return best; }; return lcs(0, 0);}Time O(n · m), space O(n · m) for the memo, plus O(n + m) for the call stack. That stack is the catch: LeetCode allows 1,000 letters per string, so the recursion can go 2,000 calls deep — past Python's default limit of 1,000. The table below has no recursion at all.
Fill the table row by row: a match takes the diagonal plus one, a mismatch takes the better of above and left.
function longestCommonSubsequence(text1, text2) { const n = text1.length, m = text2.length; // dp[i][j]: the LCS length of text1[0..i) and text2[0..j) const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (text1[i - 1] === text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; // both letters join the LCS else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); // drop one letter or the other } } return dp[n][m];}Time O(n · m), space O(n · m). Each cell is filled once from three neighbours that are already done. To recover the subsequence itself, walk back from the bottom-right cell: a diagonal step on matching letters is part of the LCS; otherwise move towards the larger neighbour.
Each row only reads the row above it, so two rows are enough — and putting the shorter string along them keeps them short.
function longestCommonSubsequence(text1, text2) { if (text2.length > text1.length) [text1, text2] = [text2, text1]; // keep the rows short const m = text2.length; let prev = new Array(m + 1).fill(0); for (let i = 1; i <= text1.length; i++) { const cur = new Array(m + 1).fill(0); for (let j = 1; j <= m; j++) { cur[j] = text1[i - 1] === text2[j - 1] ? prev[j - 1] + 1 : Math.max(prev[j], cur[j - 1]); } prev = cur; // only the row above is ever needed } return prev[m];}Time O(n · m), space O(min(n, m)). The trade-off: with only two rows kept, the walk back to recover the subsequence is no longer possible.
The table is the answer to give first — it's easy to explain and to trace by hand. Mention the two-row version when asked about memory.
"abc" and "def" give 0."aaa" and "aa" give 2: each letter is used at most once."abcde" and "ace" give 3 even though they share no two adjacent letters.dp[i][j] describes the first i letters, so it compares text1[i - 1] and text2[j - 1].s and reverse(s). Pattern 5 of this course.