1062. Longest Repeating Substring đ
Problem Description
Given a string s
, you need to find the length of the longest substring that appears at least twice in the string. The repeating substrings can overlap but must start at different positions.
For example:
- In the string
"abcabc"
, the substring"abc"
appears twice (at positions 0 and 3), so the answer would be 3 - In the string
"aabcaaba"
, the substring"aab"
appears twice (at positions 0 and 5), so the answer would be 3 - In the string
"abbaba"
, the substring"ab"
appears multiple times, and"ba"
also appears multiple times, but the longest repeating substring is"ab"
or"ba"
with length 2 - If no substring repeats (like in
"abcd"
), return 0
The key insight is that we're looking for the longest common substring between the string and itself, but ensuring the two occurrences start at different positions. The solution uses dynamic programming where f[i][j]
represents the length of the longest common substring ending at positions i
and j
where i > j
. When characters at positions i
and j
match (s[i] == s[j]
), we extend the previous matching substring length by 1. The condition i > j
ensures we're comparing different positions in the string.
Intuition
The problem asks for the longest substring that appears at least twice. This immediately suggests we need to compare the string with itself to find common substrings at different positions.
Think of it this way: if we have a repeating substring, it means there are two positions i
and j
in the string where the same sequence of characters begins. For example, in "banana"
, both positions 1 and 3 start with "ana"
.
This naturally leads to a dynamic programming approach where we compare every pair of positions in the string. We can build a 2D table where f[i][j]
tracks the length of matching substrings ending at positions i
and j
.
The key insight is that if characters at positions i
and j
match (s[i] == s[j]
), we can extend any matching substring that ended at positions i-1
and j-1
. This is similar to finding the longest common subsequence, but here we're looking for consecutive matches.
Why do we only consider j < i
? Because we want to avoid comparing a position with itself (which would always match) and we don't need to check both (i,j)
and (j,i)
since they represent the same pair of substrings.
The recurrence relation becomes clear: if s[i] == s[j]
, then f[i][j] = f[i-1][j-1] + 1
(extending the previous match by one character). Otherwise, the matching substring ends, so we don't update f[i][j]
(it remains 0).
By tracking the maximum value across all f[i][j]
, we find the length of the longest repeating substring in the entire string.
Learn more about Binary Search and Dynamic Programming patterns.
Solution Approach
We implement the solution using dynamic programming with a 2D table to track matching substrings.
Step 1: Initialize the DP table
Create a 2D array f
of size n Ă n
where n
is the length of string s
. Initialize all values to 0. Here, f[i][j]
represents the length of the longest repeating substring ending at positions i
and j
.
Step 2: Fill the DP table
We iterate through all pairs of positions where i
ranges from 1 to n-1
and j
ranges from 0 to i-1
. This ensures we only compare different positions and avoid redundant comparisons.
For each pair (i, j)
:
- If
s[i] == s[j]
, we found matching characters at different positions - We update
f[i][j]
based on whether we can extend a previous match:
The formula means:
- When
j > 0
: We can check if there was a match at(i-1, j-1)
. If yes, we extend that substring by 1 - When
j = 0
: We're at the boundary, so the matching substring has length 1
Step 3: Track the maximum length
As we fill the table, we continuously update ans
with the maximum value found in f[i][j]
. This gives us the length of the longest repeating substring.
Example walkthrough:
For string "aab"
:
- When
i=1, j=0
:s[1]='a'
equalss[0]='a'
, sof[1][0] = 1
- When
i=2, j=0
:s[2]='b'
âs[0]='a'
, sof[2][0] = 0
- When
i=2, j=1
:s[2]='b'
âs[1]='a'
, sof[2][1] = 0
The maximum value is 1, which corresponds to the repeating substring "a"
.
Time Complexity: O(n²)
- We iterate through all pairs (i, j)
where i > j
Space Complexity: O(n²)
- We use a 2D array of size n Ă n
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the string s = "ababa"
.
Step 1: Initialize
- String:
"ababa"
(indices 0-4) - Create a 5Ă5 DP table
f
, initialized with zeros ans = 0
Step 2: Fill the DP table
We iterate through pairs (i, j)
where i > j
:
When i = 1:
j = 0
: Compares[1]='b'
withs[0]='a'
â Different,f[1][0] = 0
When i = 2:
j = 0
: Compares[2]='a'
withs[0]='a'
â Match!f[2][0] = 1
, updateans = 1
j = 1
: Compares[2]='a'
withs[1]='b'
â Different,f[2][1] = 0
When i = 3:
j = 0
: Compares[3]='b'
withs[0]='a'
â Different,f[3][0] = 0
j = 1
: Compares[3]='b'
withs[1]='b'
â Match! Sincef[2][0] = 1
(previous positions matched),f[3][1] = f[2][0] + 1 = 2
, updateans = 2
j = 2
: Compares[3]='b'
withs[2]='a'
â Different,f[3][2] = 0
When i = 4:
j = 0
: Compares[4]='a'
withs[0]='a'
â Match! Sincef[3][0] = 0
(no previous match),f[4][0] = 0 + 1 = 1
j = 1
: Compares[4]='a'
withs[1]='b'
â Different,f[4][1] = 0
j = 2
: Compares[4]='a'
withs[2]='a'
â Match! Sincef[3][1] = 2
(previous positions matched),f[4][2] = f[3][1] + 1 = 3
, updateans = 3
j = 3
: Compares[4]='a'
withs[3]='b'
â Different,f[4][3] = 0
Final DP table (showing non-zero values):
0 1 2 3 4 0 0 - - - - 1 0 0 - - - 2 1 0 0 - - 3 0 2 0 0 - 4 1 0 3 0 0
Result: The maximum value is 3, corresponding to the substring "aba"
which appears at positions 0-2 and 2-4.
The key insight: When we found f[4][2] = 3
, it means the substrings ending at positions 4 and 2 share 3 consecutive matching characters. Working backwards: s[4]=s[2]='a'
, s[3]=s[1]='b'
, s[2]=s[0]='a'
, giving us the repeating substring "aba"
.
Solution Implementation
1class Solution:
2 def longestRepeatingSubstring(self, s: str) -> int:
3 """
4 Find the length of the longest repeating substring in the given string.
5 A repeating substring appears at least twice in the string without overlapping.
6
7 Args:
8 s: Input string
9
10 Returns:
11 Length of the longest repeating substring
12 """
13 n = len(s)
14
15 # dp[i][j] represents the length of common substring ending at s[i] and s[j]
16 # where i > j to avoid overlapping
17 dp = [[0] * n for _ in range(n)]
18 max_length = 0
19
20 # Iterate through all possible pairs of positions
21 for i in range(1, n):
22 for j in range(i):
23 # If characters at position i and j match
24 if s[i] == s[j]:
25 # If j > 0, extend the previous common substring
26 # Otherwise, start a new common substring of length 1
27 if j > 0:
28 dp[i][j] = dp[i - 1][j - 1] + 1
29 else:
30 dp[i][j] = 1
31
32 # Update the maximum length found so far
33 max_length = max(max_length, dp[i][j])
34
35 return max_length
36
1class Solution {
2 public int longestRepeatingSubstring(String s) {
3 int length = s.length();
4
5 // dp[i][j] represents the length of common substring ending at index i and j
6 // where i > j to ensure we're comparing different positions
7 int[][] dp = new int[length][length];
8
9 int maxLength = 0;
10
11 // Iterate through all pairs of indices where i > j
12 for (int i = 1; i < length; i++) {
13 for (int j = 0; j < i; j++) {
14 // If characters at position i and j match
15 if (s.charAt(i) == s.charAt(j)) {
16 // If j > 0, extend the previous matching substring
17 // Otherwise, start a new matching substring of length 1
18 if (j > 0) {
19 dp[i][j] = dp[i - 1][j - 1] + 1;
20 } else {
21 dp[i][j] = 1;
22 }
23
24 // Update the maximum length found so far
25 maxLength = Math.max(maxLength, dp[i][j]);
26 }
27 // If characters don't match, dp[i][j] remains 0 (default value)
28 }
29 }
30
31 return maxLength;
32 }
33}
34
1class Solution {
2public:
3 int longestRepeatingSubstring(string s) {
4 int n = s.length();
5
6 // dp[i][j] represents the length of common substring ending at s[i] and s[j]
7 // where i > j (to ensure we're comparing different positions)
8 int dp[n][n];
9 memset(dp, 0, sizeof(dp));
10
11 int maxLength = 0;
12
13 // Iterate through all pairs of positions (i, j) where i > j
14 for (int i = 1; i < n; ++i) {
15 for (int j = 0; j < i; ++j) {
16 // If characters at positions i and j match
17 if (s[i] == s[j]) {
18 // If j > 0, extend the previous matching substring
19 // Otherwise, start a new matching substring of length 1
20 dp[i][j] = 1 + (j > 0 ? dp[i - 1][j - 1] : 0);
21
22 // Update the maximum length found so far
23 maxLength = max(maxLength, dp[i][j]);
24 }
25 // If characters don't match, dp[i][j] remains 0 (already initialized)
26 }
27 }
28
29 return maxLength;
30 }
31};
32
1/**
2 * Finds the length of the longest repeating substring in a string.
3 * A repeating substring is a substring that appears at least twice in the string
4 * without overlapping.
5 *
6 * @param s - The input string to search for repeating substrings
7 * @returns The length of the longest repeating substring
8 */
9function longestRepeatingSubstring(s: string): number {
10 const stringLength: number = s.length;
11
12 // Create a 2D DP table where dp[i][j] represents the length of common substring
13 // ending at position i and position j (where j < i)
14 const dp: number[][] = Array.from({ length: stringLength }, () =>
15 Array(stringLength).fill(0)
16 );
17
18 let maxLength: number = 0;
19
20 // Iterate through all possible pairs of positions (i, j) where i > j
21 for (let i = 1; i < stringLength; i++) {
22 for (let j = 0; j < i; j++) {
23 // If characters at positions i and j match
24 if (s[i] === s[j]) {
25 // Extend the common substring length from previous positions
26 // If i-1 or j-1 is out of bounds, use 0 as the base value
27 dp[i][j] = 1 + (j > 0 ? dp[i - 1][j - 1] : 0);
28
29 // Update the maximum length found so far
30 maxLength = Math.max(maxLength, dp[i][j]);
31 }
32 }
33 }
34
35 return maxLength;
36}
37
Time and Space Complexity
Time Complexity: O(n²)
The algorithm uses two nested loops. The outer loop runs from index 1 to n-1, iterating n-1
times. The inner loop runs from index 0 to i-1 for each iteration of the outer loop. This gives us a total of 1 + 2 + 3 + ... + (n-1) = n(n-1)/2
iterations, which simplifies to O(n²)
time complexity.
Space Complexity: O(n²)
The algorithm creates a 2D array f
of size n Ă n
to store the dynamic programming states, where f[i][j]
represents the length of the longest common substring ending at positions i
and j
. This requires O(n²)
space. The only other variables used are ans
and loop counters, which take O(1)
additional space. Therefore, the overall space complexity is O(n²)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Confusing Overlapping vs Non-overlapping Substrings
A common misunderstanding is thinking that repeating substrings cannot overlap at all. In reality, they can overlap as long as they start at different positions.
Incorrect assumption: In string "aaa"
, thinking the answer is 1 because substrings cannot overlap.
Correct understanding: The substring "aa"
appears at positions 0 and 1 (overlapping), so the answer is 2.
2. Incorrect DP State Definition
Some might try to define dp[i][j]
for all i
and j
, leading to redundant calculations or incorrect results when i == j
.
Pitfall code:
for i in range(n):
for j in range(n):
if i != j and s[i] == s[j]: # Wrong: includes both (i,j) and (j,i)
dp[i][j] = dp[i-1][j-1] + 1
Solution: Always maintain i > j
to avoid counting the same pair twice and to ensure we're comparing different starting positions:
for i in range(1, n):
for j in range(i): # Ensures j < i
if s[i] == s[j]:
dp[i][j] = dp[i-1][j-1] + 1 if j > 0 else 1
3. Space Optimization Trap
Attempting to optimize space by using a 1D array instead of 2D without careful consideration of dependencies.
Incorrect optimization:
dp = [0] * n # Wrong: loses information about different starting positions
for i in range(1, n):
if s[i] == s[i-1]:
dp[i] = dp[i-1] + 1 # This only finds consecutive repeating characters
Correct space optimization: If space is a concern, use a rolling array technique:
prev = [0] * n
curr = [0] * n
for i in range(1, n):
for j in range(i):
if s[i] == s[j]:
curr[j] = prev[j-1] + 1 if j > 0 else 1
max_length = max(max_length, curr[j])
prev, curr = curr, [0] * n
4. Edge Case Handling
Forgetting to handle strings with length 0 or 1, which cannot have repeating substrings.
Missing edge case:
def longestRepeatingSubstring(self, s: str) -> int:
n = len(s)
dp = [[0] * n for _ in range(n)] # Fails when n = 0
Solution:
def longestRepeatingSubstring(self, s: str) -> int:
n = len(s)
if n < 2:
return 0
dp = [[0] * n for _ in range(n)]
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donât Miss This!