Longest Common Substring in DSA Typescript - Time & Space Complexity
We want to understand how the time needed to find the longest common substring between two strings grows as the strings get longer.
How does the work increase when the input strings become bigger?
Analyze the time complexity of the following code snippet.
function longestCommonSubstring(s1: string, s2: string): number {
const m = s1.length;
const n = s2.length;
let maxLen = 0;
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;
maxLen = Math.max(maxLen, dp[i][j]);
}
}
}
return maxLen;
}
This code finds the length of the longest substring that appears in both input strings by using a table to store matching lengths.
- Primary operation: Nested loops comparing characters of both strings and updating the table.
- How many times: The outer loop runs m times, and for each, the inner loop runs n times, so total m x n times.
As the lengths of the two strings grow, the number of comparisons grows by multiplying their lengths.
| Input Size (m and n) | Approx. Operations |
|---|---|
| 10 and 10 | 100 |
| 100 and 100 | 10,000 |
| 1000 and 1000 | 1,000,000 |
Pattern observation: Doubling the length of both strings roughly quadruples the work because the loops multiply.
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 only depends on the length of one string, so it is O(n)."
[OK] Correct: The algorithm compares every character of the first string with every character of the second, so both lengths matter and multiply.
Understanding this time complexity helps you explain how dynamic programming solutions scale and why nested loops are common in string comparison problems.
"What if we only needed to find the longest common prefix instead of substring? How would the time complexity change?"
Practice
s1 and s2?Solution
Step 1: Understand substring vs subsequence
A substring is a continuous part of a string, while a subsequence can skip characters.Step 2: Define Longest Common Substring
It finds the longest continuous sequence of characters present in both strings.Final Answer:
The longest sequence of characters that appear contiguously in both strings -> Option CQuick Check:
Longest Common Substring = continuous match [OK]
- Confusing substring with subsequence
- Thinking order doesn't matter
- Counting total common characters instead
s1 and s2?Solution
Step 1: Identify input types
The inputs are two strings, so parameters should bestring.Step 2: Identify output type
The function returns the length of the longest common substring, which is a number.Final Answer:
function longestCommonSubstring(s1: string, s2: string): number -> Option AQuick Check:
Input strings, output number length [OK]
- Using arrays instead of strings as input
- Returning string or array instead of number
- Using void return type mistakenly
function longestCommonSubstring(s1: string, s2: string): number {
const m = s1.length, n = s2.length;
let maxLen = 0;
const dp: number[][] = Array(m + 1).fill(0).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]);
}
}
}
return maxLen;
}
console.log(longestCommonSubstring('abcde', 'abfde'));Solution
Step 1: Compare characters of 'abcde' and 'abfde'
Matching continuous substrings are 'ab' at start and 'de' at end, both length 2.Step 2: Find longest continuous match
Longest common substring length is 2.Final Answer:
2 -> Option AQuick Check:
Longest common substring length = 2 [OK]
- Counting non-continuous matches
- Confusing longest common subsequence with substring
- Off-by-one errors in indexing
function longestCommonSubstring(s1: string, s2: string): number {
const m = s1.length, n = s2.length;
let maxLen = 0;
const dp: number[][] = Array(m).fill(0).map(() => 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;
maxLen = Math.max(maxLen, dp[i][j]);
}
}
}
return maxLen;
}Solution
Step 1: Check dp array initialization
dp is created with size m x n but accessed with indices up to m and n (1-based), causing out-of-bound errors.Step 2: Correct dp size
dp should be (m+1) x (n+1) to safely use 1-based indexing in loops.Final Answer:
dp array is initialized with wrong size causing index errors -> Option BQuick Check:
dp size must match loop indices [OK]
- Not adding +1 to dp dimensions
- Starting loops at 1 but dp size too small
- Ignoring index out-of-bound runtime errors
s1 = 'xabxac' and s2 = 'abcabxabcd', what is the length of their longest common substring?Solution
Step 1: Identify common substrings
Common substrings include 'abx' and 'abxa'. 'abxa' is length 4 and appears in both strings.Step 2: Confirm longest substring length
No longer continuous substring than length 4 exists in both strings.Final Answer:
4 -> Option DQuick Check:
Longest common substring length = 4 [OK]
- Counting non-continuous matches
- Choosing shorter common substrings
- Confusing substring with subsequence
Ready to prove it?
Pass the test and this pattern moves unknown
