Longest Common Subsequence in DSA Javascript - Time & Space Complexity
We want to understand how the time needed to find the longest common subsequence grows as the input strings get longer.
How does the work increase when the strings become bigger?
Analyze the time complexity of the following code snippet.
function lcs(str1, str2) {
const m = str1.length, n = str2.length;
const dp = 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 (str1[i - 1] === str2[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 strings get longer, 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 two string lengths.
[X] Wrong: "The algorithm runs in linear time because it just loops through the strings once."
[OK] Correct: The algorithm uses nested loops, so it compares every character of one string with every character of the other, making it slower than just one loop.
Understanding this time complexity helps you explain how dynamic programming improves efficiency by avoiding repeated work, a key skill in problem solving.
"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 about finding a sequence that appears in both strings in the same order, but characters do not need to be next to each other.Step 2: Differentiate LCS from substring
A substring requires characters to be consecutive, but LCS allows gaps, so it is not necessarily a substring.Final Answer:
The longest sequence of characters that appear in both strings in the same order, but not necessarily consecutively -> Option AQuick Check:
LCS means longest common sequence, not substring [OK]
- Confusing LCS with longest common substring
- Thinking characters must be consecutive
- Assuming LCS is the combined length
dp of size (m+1) x (n+1) with zeros in JavaScript for LCS dynamic programming?Solution
Step 1: Understand how to create independent inner arrays
UsingArray.fromwith a function creates new inner arrays for each row, avoiding shared references.Step 2: Analyze each option
Array(m+1).fill(Array(n+1).fill(0))shares the same inner array for all rows, causing bugs. Options C and D pass an array tomap()instead of a function, causing a TypeError.Array.from({length: m+1}, () => Array(n+1).fill(0))correctly creates independent arrays.Final Answer:
const dp = Array.from({length: m+1}, () => Array(n+1).fill(0)); -> Option BQuick Check:
Use Array.from with function for 2D arrays [OK]
- Using fill with array directly causing shared references
- Passing an array to map() instead of a function
- Not initializing inner arrays properly
s1 = 'abcde' and s2 = 'ace'?
function lcsLength(s1, s2) {
const m = s1.length, n = s2.length;
const dp = Array.from({length: m+1}, () => 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('abcde', 'ace'));Solution
Step 1: Trace matching characters between 'abcde' and 'ace'
The common subsequence is 'ace' with length 3.Step 2: Confirm DP calculation
The code correctly fills dp table and returns dp[5][3] = 3 as the LCS length.Final Answer:
3 -> Option CQuick Check:
LCS('abcde','ace') = 'ace' length 3 [OK]
- Counting only consecutive matches (substring)
- Confusing length with total characters
- Off-by-one errors in indexing
function lcsLength(s1, s2) {
const m = s1.length, n = s2.length;
const dp = Array(m+1).fill(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];
}Solution
Step 1: Analyze dp initialization
Using fill with an array fills dp with references to the same inner array, so all rows share one array.Step 2: Understand impact on updates
Updating dp[i][j] changes all rows at column j, corrupting the DP table and causing wrong results.Final Answer:
The inner arrays in dp share the same reference causing incorrect updates -> Option AQuick Check:
Array fill shares references, use Array.from instead [OK]
- Using fill with arrays causing shared references
- Starting loops at 0 causing index errors
- Incorrect character comparison
s1 = 'AGGTAB' and s2 = 'GXTXAYB', which of the following is a valid Longest Common Subsequence (LCS)?Solution
Step 1: Identify common subsequences in order
Check which sequences appear in both strings in order without rearranging characters.Step 2: Validate each option
"GTAB" appears in both strings in order. Options A, B, and C contain characters out of order or extra characters not matching subsequence rules.Final Answer:
"GTAB" -> Option DQuick Check:
Valid LCS must appear in both strings in order [OK]
- Choosing sequences with characters out of order
- Including characters not in both strings
- Confusing subsequence with substring
Ready to prove it?
Pass the test and this pattern moves unknown
