Longest Common Substring in DSA Go - 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 work increase when the strings become bigger?
Analyze the time complexity of the following code snippet.
func longestCommonSubstring(s1, s2 string) int {
m, n := len(s1), len(s2)
maxLen := 0
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if s1[i-1] == s2[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
if dp[i][j] > maxLen {
maxLen = dp[i][j]
}
}
}
}
return maxLen
}
This code finds the length of the longest substring that appears in both strings by checking all pairs of characters and building up a table.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Nested loops over both strings to fill the dp table.
- How many times: The inner code runs once for each pair of characters, so m x n times.
As the strings get longer, the work grows by multiplying their lengths.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 100 |
| 100 | 10,000 |
| 1000 | 1,000,000 |
Pattern observation: Doubling the length roughly squares the work, because every character in one string is compared with every character in the other.
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 the work.
Understanding this time complexity helps you explain how dynamic programming solves problems by breaking them into smaller parts and why nested loops often lead to multiplied work.
"What if we used a suffix tree instead of dynamic programming? How would the time complexity change?"
Practice
s1 and s2?Solution
Step 1: Understand the problem definition
The Longest Common Substring problem looks for the longest sequence of characters that appear consecutively in both strings.Step 2: Differentiate from similar problems
Unlike subsequence problems, substring requires characters to be continuous and in the same order in both strings.Final Answer:
The longest sequence of characters that appear consecutively in both strings -> Option BQuick Check:
Longest Common Substring = consecutive matching characters [OK]
- Confusing substring with subsequence
- Thinking order does not matter
- Counting total common characters instead of longest sequence
dp for Longest Common Substring of lengths m and n?Solution
Step 1: Understand Go slice initialization
To create a 2D slice, first create the outer slice with length m+1, then initialize each inner slice with length n+1.Step 2: Check each option
dp := make([][]int, m+1) for i := range dp { dp[i] = make([]int, n+1) } correctly creates dp as [][]int with proper inner slices. dp := make([]int, m+1) for i := range dp { dp[i] = make([]int, n+1) } tries to assign inner slices to int slice, invalid. var dp [m+1][n+1]int uses array syntax but m,n are variables, so invalid. dp := [][]int{{m+1}, {n+1}} creates a slice with two elements, not a 2D slice.Final Answer:
dp := make([][]int, m+1) for i := range dp { dp[i] = make([]int, n+1) } -> Option DQuick Check:
Proper 2D slice initialization = dp := make([][]int, m+1) for i := range dp { dp[i] = make([]int, n+1) } [OK]
- Using make for inner slices on a 1D int slice
- Trying to declare arrays with variable sizes
- Incorrect slice literal syntax
s1 = "abcde" and s2 = "abfde"?
func longestCommonSubstring(s1, s2 string) int {
m, n := len(s1), len(s2)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
maxLen := 0
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if s1[i-1] == s2[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
if dp[i][j] > maxLen {
maxLen = dp[i][j]
}
}
}
}
return maxLen
}
fmt.Println(longestCommonSubstring("abcde", "abfde"))Solution
Step 1: Trace matching characters
Compare characters of "abcde" and "abfde": - 'a' == 'a' (dp[1][1] = 1) - 'b' == 'b' (dp[2][2] = 2) - 'c' != 'f' (reset) - 'd' == 'd' (dp[4][4] = 1) - 'e' == 'e' (dp[5][5] = 2)Step 2: Find longest continuous match
The longest continuous matches are "ab" (length 2) and "de" (length 2). No longer sequence exists. Thus, maxLen = 2.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
func longestCommonSubstring(s1, s2 string) int {
m, n := len(s1), len(s2)
dp := make([][]int, m)
for i := range dp {
dp[i] = make([]int, n)
}
maxLen := 0
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if s1[i] == s2[j] {
dp[i][j] = dp[i-1][j-1] + 1
if dp[i][j] > maxLen {
maxLen = dp[i][j]
}
}
}
}
return maxLen
}Solution
Step 1: Analyze dp slice size
The dp slice is created with size m and n, but the code accesses dp[i-1][j-1] which can cause index out of range when i or j is 0.Step 2: Correct dp dimensions
To safely access dp[i-1][j-1], dp should be sized (m+1) x (n+1) and loops should run from 1 to m and n, avoiding negative indices.Final Answer:
dp slice dimensions should be m+1 and n+1 to avoid index errors -> Option CQuick Check:
dp size must allow safe dp[i-1][j-1] access [OK]
- Using dp size m and n causing index errors
- Starting loops at 0 causing negative index access
- Incorrect initialization of maxLen
s1 = "ababc" and s2 = "babca", write a Go function to find the longest common substring and return the substring itself (not just length). What will be the returned substring?Solution
Step 1: Identify longest common substrings
Check substrings common to both: - "ababc" vs "babca" share "babc" as longest common substring of length 4. - "abca" is not a substring of s1. - "babc" appears in both strings consecutively.Step 2: Confirm substring returned
The longest common substring is "babc" with length 4, so the function should return "babc".Final Answer:
"babc" -> Option AQuick Check:
Longest common substring = "babc" [OK]
- Returning longest common subsequence instead
- Confusing substring with subsequence
- Returning multiple substrings instead of one
Ready to prove it?
Pass the test and this pattern moves unknown
