Longest Common Subsequence in DSA Typescript - Time & Space Complexity
We want to understand how the time needed to find the longest common subsequence grows as the input strings get longer.
The question is: how does the work increase when the strings become bigger?
Analyze the time complexity of the following code snippet.
function lcs(X: string, Y: string): number {
const m = X.length, n = Y.length;
const dp: number[][] = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (X[i - 1] === Y[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[m][n];
}
This code finds the length of the longest common subsequence between two strings using a table to store results.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Nested loops over both strings to fill the dp table.
- How many times: Outer loop runs m times, inner loop runs n times, so total m x n times.
As the lengths of the two strings grow, the number of operations grows by multiplying their lengths.
| Input Size (m, n) | Approx. Operations |
|---|---|
| 10, 10 | 100 |
| 100, 100 | 10,000 |
| 1000, 1000 | 1,000,000 |
Pattern observation: Doubling the length of both strings roughly quadruples the work.
Time Complexity: O(m x n)
This means the time needed grows proportionally to the product of the lengths of the two input strings.
[X] Wrong: "The algorithm runs in linear time because it just loops through the strings once."
[OK] Correct: Actually, it uses nested loops, so it compares every character of one string with every character of the other, making the work multiply.
Understanding this time complexity helps you explain how dynamic programming improves efficiency and why nested loops matter in real problems.
"What if we used recursion without memoization? How would the time complexity change?"
Practice
Solution
Step 1: Understand the definition of LCS
The LCS is the longest sequence that appears in both strings in the same order but not necessarily next to each other.Step 2: Differentiate from substring and edit distance
Unlike substring, LCS does not require characters to be consecutive. It is not about shortest edits either.Final Answer:
The longest sequence of characters that appear in both strings in the same order, but not necessarily consecutively -> Option BQuick Check:
LCS means longest common sequence, not substring or edits [OK]
- Confusing LCS with longest common substring
- Thinking LCS requires consecutive characters
- Mixing LCS with edit distance or shortest transformation
s1 and s2?Solution
Step 1: Identify input and output types for LCS length
The function takes two strings and returns a number representing the length of their LCS.Step 2: Check each option's signature
function lcsLength(s1: string, s2: string): number matches input strings and returns a number. Others either use wrong input types or return wrong types.Final Answer:
function lcsLength(s1: string, s2: string): number -> Option DQuick Check:
Input strings, output number for LCS length [OK]
- Using arrays instead of strings as input
- Returning string or void instead of number
- Confusing LCS length with LCS sequence output
function lcsLength(s1: string, s2: string): number {
const m = s1.length, n = s2.length;
const dp: number[][] = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s1[i - 1] === s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
console.log(lcsLength('AGGTAB', 'GXTXAYB'))Solution
Step 1: Understand the LCS dynamic programming table
The code builds a 2D table dp where dp[i][j] stores LCS length of s1[0..i-1] and s2[0..j-1].Step 2: Trace LCS for 'AGGTAB' and 'GXTXAYB'
The LCS is 'GTAB' with length 4, but careful tracing shows length 4 is correct. However, the known LCS length for these strings is 4.Final Answer:
4 -> Option AQuick Check:
LCS('AGGTAB','GXTXAYB') = 4 [OK]
- Confusing LCS length with substring length
- Miscounting matching characters
- Off-by-one errors in indexing
function lcsLength(s1: string, s2: string): number {
const m = s1.length, n = s2.length;
const dp: number[][] = Array(m).fill(Array(n).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s1[i - 1] === s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m - 1][n - 1];
}Solution
Step 1: Analyze dp array initialization
Using Array(m).fill(Array(n).fill(0)) creates m references to the same inner array, causing shared rows.Step 2: Understand impact on dp updates
Updating dp[i][j] affects all rows because they point to the same array, corrupting the dp table.Final Answer:
dp array initialization shares inner arrays causing incorrect updates -> Option AQuick Check:
Array fill with same inner array causes shared references [OK]
- Using fill with array causes shared references
- Incorrect dp return indices
- Off-by-one errors in loops
s1 = 'ABCBDAB' and s2 = 'BDCABA', which of the following is a valid Longest Common Subsequence?Solution
Step 1: Determine length of LCS
The LCS length for these strings is 4.Step 2: Check each option for validity and length
'BDAB' is length 4 and appears in order in both strings. Others are either shorter or invalid sequences.Final Answer:
'BDAB' -> Option CQuick Check:
Valid LCS must be subsequence and max length 4 [OK]
- Choosing subsequences not in order in both strings
- Picking shorter subsequences ignoring max length
- Confusing substring with subsequence
Ready to prove it?
Pass the test and this pattern moves unknown
