Longest Common Substring in DSA Javascript - Time & Space Complexity
We want to understand how the time needed to find the longest common substring grows as the input strings get longer.
How does the number of steps change when the strings increase in size?
Analyze the time complexity of the following code snippet.
function longestCommonSubstring(s1, s2) {
const m = s1.length, n = s2.length;
let maxLen = 0;
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 (s1[i - 1] === s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
maxLen = Math.max(maxLen, dp[i][j]);
} else {
dp[i][j] = 0;
}
}
}
return maxLen;
}
This code finds the length of the longest substring common to both input strings using a 2D table.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Nested loops over both strings comparing characters and updating the 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 lengths of the two strings.
[X] Wrong: "The algorithm runs in linear time because it just loops through the strings once."
[OK] Correct: The code uses nested loops, so it compares every character of one string with every character of the other, making the work multiply, not just add.
Understanding this time complexity helps you explain how dynamic programming solves problems by breaking them into smaller parts efficiently.
"What if we only needed to find the longest common prefix instead of substring? How would the time complexity change?"
Practice
Solution
Step 1: Understand the problem definition
The Longest Common Substring problem looks for the longest continuous sequence of characters shared by both strings.Step 2: Differentiate from similar problems
Unlike Longest Common Subsequence, the substring must be consecutive characters, not scattered.Final Answer:
The longest sequence of characters that appear consecutively in both strings -> Option DQuick Check:
Longest Common Substring = consecutive matching characters [OK]
- Confusing substring with subsequence
- Thinking order does not matter
- Ignoring continuity requirement
Solution
Step 1: Understand 2D array initialization in JavaScript
Using Array.from with a mapping function creates independent inner arrays, avoiding shared references.Step 2: Analyze each option
Options B and C use fill with arrays, causing all rows to reference the same inner array. const dp = Array(m + 1).map(Array(n + 1).fill(0)); passes Array(n + 1).fill(0) directly to map, which expects a function and causes TypeError.Final Answer:
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); -> Option BQuick Check:
Use Array.from with function for independent rows [OK]
- Using fill with arrays causing shared references
- Using map on uninitialized arrays
- Not initializing inner arrays properly
function longestCommonSubstring(s1, s2) {
const m = s1.length, n = s2.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
let maxLen = 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;
maxLen = Math.max(maxLen, dp[i][j]);
}
}
}
return maxLen;
}
console.log(longestCommonSubstring('abcde', 'abfde'));Solution
Step 1: Trace matching characters between 'abcde' and 'abfde'
Matching substrings are 'ab' at start and 'de' at end. Both have length 2.Step 2: Identify longest common substring length
Longest common substring length is 2, from either 'ab' or 'de'.Final Answer:
2 -> Option AQuick Check:
Longest common substring length = 2 [OK]
- Counting non-continuous matches
- Confusing substring length with subsequence length
- 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));
let maxLen = 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;
maxLen = Math.max(maxLen, dp[i][j]);
}
}
}
return maxLen;
}Solution
Step 1: Analyze dp array initialization
Using fill with an array causes all rows to reference the same inner array, so updates affect all rows.Step 2: Understand impact on dp updates
Because rows share the same array, dp[i][j] updates overwrite other rows, corrupting the dp table and results.Final Answer:
dp array rows share the same inner array causing incorrect updates -> Option AQuick Check:
Shared inner arrays cause dp corruption [OK]
- Using fill for 2D arrays causing shared references
- Ignoring dp table corruption
- Misunderstanding loop indices
s1 = 'xabxac' and s2 = 'abcabxabcd', what is the longest common substring and its length?Solution
Step 1: Identify common substrings between s1 and s2
Check substrings of s1 in s2: 'xab' appears, 'abx' appears, 'abxa' appears in s2 starting at index 4.Step 2: Find the longest continuous match
'abxa' is length 4 and appears in both strings continuously, longer than other matches.Final Answer:
'abxa', length 4 -> Option CQuick Check:
Longest common substring = 'abxa' length 4 [OK]
- Choosing shorter substrings ignoring longer matches
- Confusing substring with subsequence
- Missing overlapping matches
Ready to prove it?
Pass the test and this pattern moves unknown
