LeetCode 718 - Maximum Length of Repeated Subarray
The problem asks us to find the longest contiguous subarray that appears in both input arrays. A subarray is different from a subsequence because the elements must remain adjacent.
Difficulty: 🟡 Medium
Topics: Array, Binary Search, Dynamic Programming, Sliding Window, Rolling Hash, Hash Function
Solution
Problem Understanding
The problem asks us to find the longest contiguous subarray that appears in both input arrays. A subarray is different from a subsequence because the elements must remain adjacent. This distinction is extremely important because many similar problems allow gaps, but this one does not.
We are given two integer arrays, nums1 and nums2. Our task is to determine the maximum possible length of a contiguous sequence that exists in both arrays. The actual subarray does not need to be returned, only its length.
For example, consider:
nums1 = [1,2,3,2,1]
nums2 = [3,2,1,4,7]
The longest shared contiguous subarray is:
[3,2,1]
Its length is 3, so the answer is 3.
The constraints are relatively small:
- Each array length is at most
1000 - Values range from
0to100
These constraints strongly suggest that an O(n^2) dynamic programming solution is feasible, because 1000 * 1000 = 1,000,000, which is manageable.
Several edge cases are important to consider:
- Arrays with all identical values
- Arrays with no common elements
- Arrays of length
1 - Repeated values that create overlapping matching regions
- Cases where the longest match occurs near the end of the arrays
A naive implementation can easily make mistakes by confusing subsequences with subarrays. Another common bug is failing to reset state when elements stop matching.
Approaches
Brute Force Approach
The brute force solution checks every possible starting position in nums1 against every possible starting position in nums2.
For each pair of starting indices (i, j), we continue moving forward while the elements match:
nums1[i + k] == nums2[j + k]
We count how long the match continues and update the global maximum.
This approach is correct because it explicitly examines every possible aligned subarray pair. If a repeated subarray exists, the brute force method will eventually compare those exact positions and measure its length.
However, the performance is poor. There are O(n * m) starting pairs, and each comparison may scan up to O(min(n, m)) elements. This leads to cubic behavior in the worst case.
Key Insight for an Optimal Solution
The important observation is that contiguous matching behaves similarly to the Longest Common Substring problem.
If:
nums1[i] == nums2[j]
then the length of the repeated subarray ending at those positions depends entirely on the previous positions:
dp[i][j] = dp[i-1][j-1] + 1
Otherwise:
dp[i][j] = 0
This works because contiguous matching requires adjacent elements to continue matching. Once two elements differ, the contiguous streak immediately breaks.
Dynamic programming allows us to reuse previously computed matching lengths instead of repeatedly scanning forward.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n × m × min(n,m)) | O(1) | Tries every possible alignment and expands matches manually |
| Optimal Dynamic Programming | O(n × m) | O(n × m) | Reuses overlapping subproblems to compute matching streaks efficiently |
Algorithm Walkthrough
Optimal Dynamic Programming Algorithm
- Create a 2D DP table where
dp[i][j]represents the length of the longest common subarray ending atnums1[i-1]andnums2[j-1]. - Initialize the table with zeros. A value of zero means the current elements do not continue a matching contiguous sequence.
- Iterate through every index in
nums1. - For each element in
nums1, iterate through every index innums2. - Compare the current elements:
- If
nums1[i-1] == nums2[j-1], then the current matching subarray extends the previous diagonal match:
dp[i][j] = dp[i-1][j-1] + 1
- Otherwise, the contiguous match breaks:
dp[i][j] = 0
- After updating each DP cell, update the global maximum length if the current value is larger.
- After processing the entire table, return the maximum value found.
Why it works
The DP table maintains an important invariant:
dp[i][j]
always stores the exact length of the longest common contiguous subarray ending at those two positions.
If the current elements match, the only way to extend a contiguous subarray is through the immediately previous pair of indices. Therefore, adding 1 to the diagonal value correctly extends the matching streak.
If the elements differ, continuity is broken, so the value must reset to 0.
Because every possible ending position pair is examined, the algorithm guarantees that the global maximum repeated subarray length is found.
Python Solution
from typing import List
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
n = len(nums1)
m = len(nums2)
dp = [[0] * (m + 1) for _ in range(n + 1)]
max_length = 0
for i in range(1, n + 1):
for j in range(1, m + 1):
if nums1[i - 1] == nums2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
max_length = max(max_length, dp[i][j])
return max_length
The implementation begins by determining the lengths of both arrays. A DP table of size (n + 1) × (m + 1) is created to simplify boundary handling. The extra row and column eliminate the need for special checks when accessing dp[i-1][j-1].
The nested loops iterate through every pair of positions in the two arrays. Whenever matching values are found, the current DP cell becomes one greater than the diagonal predecessor.
The diagonal dependency is critical because contiguous subarrays can only extend from immediately previous matching positions.
The variable max_length tracks the largest matching subarray seen so far. Since every DP cell represents a valid repeated subarray ending at those indices, the maximum over all cells is the final answer.
Go Solution
func findLength(nums1 []int, nums2 []int) int {
n := len(nums1)
m := len(nums2)
dp := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, m+1)
}
maxLength := 0
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
if nums1[i-1] == nums2[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
if dp[i][j] > maxLength {
maxLength = dp[i][j]
}
}
}
}
return maxLength
}
The Go implementation closely mirrors the Python solution. The primary difference is explicit slice allocation for the 2D DP table.
Go arrays and slices are zero-initialized automatically, so unmatched states naturally remain 0.
Integer overflow is not a concern because the maximum possible answer is only 1000.
Unlike Python, Go does not require importing type annotations or helper libraries.
Worked Examples
Example 1
Input:
nums1 = [1,2,3,2,1]
nums2 = [3,2,1,4,7]
We build the DP table progressively.
| i | j | nums1[i-1] | nums2[j-1] | Match? | dp[i][j] | max_length |
|---|---|---|---|---|---|---|
| 1 | 3 | 1 | 1 | Yes | 1 | 1 |
| 2 | 2 | 2 | 2 | Yes | 1 | 1 |
| 3 | 1 | 3 | 3 | Yes | 1 | 1 |
| 4 | 2 | 2 | 2 | Yes | 2 | 2 |
| 5 | 3 | 1 | 1 | Yes | 3 | 3 |
The final maximum becomes 3.
The repeated subarray is:
[3,2,1]
Example 2
Input:
nums1 = [0,0,0,0,0]
nums2 = [0,0,0,0,0]
Since every element matches, the diagonal values continuously grow.
| i | j | dp[i][j] |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
| 4 | 4 | 4 |
| 5 | 5 | 5 |
The answer becomes 5.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n × m) | Every pair of indices is processed exactly once |
| Space | O(n × m) | The DP table stores results for all index pairs |
The algorithm uses nested loops over both arrays, giving a total of n * m states. Each state performs only constant-time work.
The DP table requires storing one integer per state, resulting in quadratic space usage.
Test Cases
from typing import List
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
n = len(nums1)
m = len(nums2)
dp = [[0] * (m + 1) for _ in range(n + 1)]
max_length = 0
for i in range(1, n + 1):
for j in range(1, m + 1):
if nums1[i - 1] == nums2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
max_length = max(max_length, dp[i][j])
return max_length
solution = Solution()
assert solution.findLength([1,2,3,2,1], [3,2,1,4,7]) == 3 # standard example
assert solution.findLength([0,0,0,0,0], [0,0,0,0,0]) == 5 # all elements match
assert solution.findLength([1], [1]) == 1 # single matching element
assert solution.findLength([1], [2]) == 0 # single non-matching element
assert solution.findLength([1,2,3], [4,5,6]) == 0 # no common subarray
assert solution.findLength([1,2,3,4], [2,3]) == 2 # subarray inside larger array
assert solution.findLength([1,1,1,1], [1,1]) == 2 # repeated identical values
assert solution.findLength([1,2,3,2,1], [1,2,3,2,1]) == 5 # identical arrays
assert solution.findLength([5,6,7,8], [7,8,9]) == 2 # matching suffix/prefix
assert solution.findLength([1,2,1,2,1], [1,2,1,2,3]) == 4 # overlapping repeated patterns
| Test | Why |
|---|---|
[1,2,3,2,1] vs [3,2,1,4,7] |
Validates standard overlapping subarray |
[0,0,0,0,0] vs [0,0,0,0,0] |
Tests maximum continuous growth |
[1] vs [1] |
Smallest matching arrays |
[1] vs [2] |
Smallest non-matching arrays |
[1,2,3] vs [4,5,6] |
Ensures zero is returned correctly |
[1,2,3,4] vs [2,3] |
Tests contained subarray |
[1,1,1,1] vs [1,1] |
Validates repeated values |
| Identical arrays | Ensures full-array match works |
| Matching suffix/prefix | Tests edge alignment behavior |
| Overlapping repeated patterns | Validates correct contiguous tracking |
Edge Cases
Arrays With No Common Elements
A common mistake is accidentally treating unrelated values as partial matches. For example:
nums1 = [1,2,3]
nums2 = [4,5,6]
No elements match, so every DP cell remains 0. The implementation handles this naturally because values are only extended when the current elements are equal.
Arrays With All Identical Values
Consider:
nums1 = [0,0,0,0,0]
nums2 = [0,0,0,0,0]
Every diagonal extension continues growing. Some incorrect implementations reset values improperly or fail to track the longest streak. The DP recurrence correctly builds increasing values along the diagonal until the answer reaches the full array length.
Repeated Overlapping Patterns
Cases like:
nums1 = [1,2,1,2,1]
nums2 = [1,2,1,2,3]
can confuse naive implementations because multiple potential matches overlap. The DP solution avoids ambiguity because each state strictly represents contiguous matches ending at specific indices. This guarantees that overlapping regions are handled correctly without double counting or skipping possibilities.