1477. Find Two Non-overlapping Sub-arrays Each With Target Sum
Problem Description
You are given an array of integers arr
and an integer target
.
Your task is to find two non-overlapping subarrays from arr
where each subarray has a sum equal to target
. Since there can be multiple valid pairs of subarrays, you need to find the pair where the sum of the lengths of the two subarrays is minimum.
A subarray is a contiguous sequence of elements from the array. Two subarrays are non-overlapping if they don't share any elements at the same indices.
Return the minimum sum of the lengths of the two required subarrays. If it's not possible to find two such non-overlapping subarrays, return -1
.
For example:
- If
arr = [3,2,2,4,3]
andtarget = 3
, you can find subarrays[3]
(length 1) and[3]
(length 1), giving a minimum sum of lengths = 2 - If
arr = [7,3,4,7]
andtarget = 7
, you can find subarrays[7]
(length 1) and[3,4]
(length 2), giving a minimum sum of lengths = 3 - If no two non-overlapping subarrays with sum equal to
target
exist, return -1
Intuition
The key insight is that we need to find two non-overlapping subarrays with sum equal to target
, and we want to minimize their combined length. This suggests we should track the shortest valid subarray we've seen so far as we traverse the array.
As we move through the array from left to right, at each position i
, we can consider two scenarios:
- The current position could be the end of a subarray with sum equal to
target
- We might have already found a valid subarray to the left of the current position
To efficiently find subarrays with a specific sum, we can use the prefix sum technique. If we maintain a running sum s
as we traverse the array, then a subarray from index j
to i
has sum equal to target
if s[i] - s[j] = target
, which means s[j] = s[i] - target
.
Using a hash table to store the positions where each prefix sum occurs allows us to quickly check if there's a valid starting point j
for a subarray ending at position i
.
The dynamic programming aspect comes from maintaining f[i]
, which represents the minimum length of any subarray with sum equal to target
found in the first i
elements. This helps us keep track of the best (shortest) subarray we've found so far on the left side.
When we find a new valid subarray from position j
to i
, we can:
- Update
f[i]
to be the minimum of the previous best and the current subarray's length(i - j)
- Check if combining this new subarray with the best subarray found before position
j
(which isf[j]
) gives us a better overall answer
This approach ensures we consider all possible pairs of non-overlapping subarrays while maintaining the minimum combined length throughout the traversal.
Learn more about Binary Search, Dynamic Programming and Sliding Window patterns.
Solution Approach
We implement the solution using hash table, prefix sum, and dynamic programming techniques.
Data Structures:
- Hash table
d
to store the most recent position where each prefix sum appears, initialized withd[0] = 0
- Array
f
of sizen+1
wheref[i]
represents the minimum length of a subarray with sum equal totarget
among the firsti
elements, initialized with infinity - Variable
s
to maintain the running prefix sum - Variable
ans
to track the minimum sum of lengths, initialized with infinity
Algorithm Steps:
-
Initialize the hash table: Set
d[0] = 0
to handle cases where the subarray starts from index 0. -
Iterate through the array: For each element at position
i
(1-indexed):- Add the current element to the prefix sum:
s += arr[i-1]
- Copy the previous best:
f[i] = f[i-1]
- Add the current element to the prefix sum:
-
Check for valid subarray ending at position i:
- If
s - target
exists in the hash tabled
, it means there's a subarray with sum equal totarget
- Let
j = d[s - target]
be the starting position of this subarray - The length of this subarray is
i - j
- If
-
Update the dynamic programming state:
- Update
f[i]
to be the minimum of its current value and the new subarray length:f[i] = min(f[i], i - j)
- This ensures
f[i]
always holds the shortest valid subarray found in the firsti
elements
- Update
-
Update the answer:
- Consider pairing the current subarray (from
j
toi
) with the best subarray found before positionj
- The combined length would be
f[j] + (i - j)
- Update the answer:
ans = min(ans, f[j] + i - j)
- Consider pairing the current subarray (from
-
Update the hash table: Store the current position for the current prefix sum:
d[s] = i
-
Return the result:
- If
ans
is still greater than the array lengthn
, it means we couldn't find two valid subarrays, so return-1
- Otherwise, return
ans
- If
The algorithm runs in O(n)
time complexity with O(n)
space complexity, efficiently finding the minimum sum of lengths of two non-overlapping subarrays with the target sum.
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 arr = [3, 2, 2, 4, 3]
and target = 3
.
Initial Setup:
n = 5
d = {0: 0}
(hash table for prefix sums and their positions)f = [0, ∞, ∞, ∞, ∞, ∞]
(minimum subarray lengths)s = 0
(prefix sum)ans = ∞
(minimum combined length)
Iteration 1 (i=1, element=3):
- Update prefix sum:
s = 0 + 3 = 3
- Copy previous best:
f[1] = f[0] = 0
- Check if
s - target = 3 - 3 = 0
exists ind
: YES, at positionj = 0
- Subarray found: from position 0 to 1, length =
1 - 0 = 1
- Update
f[1] = min(∞, 1) = 1
- Update answer:
ans = min(∞, f[0] + 1) = min(∞, 0 + 1) = 1
(but we need 2 subarrays, so this just tracks potential) - Update hash table:
d[3] = 1
- State:
d = {0: 0, 3: 1}
,f = [0, 1, ∞, ∞, ∞, ∞]
Iteration 2 (i=2, element=2):
- Update prefix sum:
s = 3 + 2 = 5
- Copy previous best:
f[2] = f[1] = 1
- Check if
s - target = 5 - 3 = 2
exists ind
: NO - No new subarray found
- Update hash table:
d[5] = 2
- State:
d = {0: 0, 3: 1, 5: 2}
,f = [0, 1, 1, ∞, ∞, ∞]
Iteration 3 (i=3, element=2):
- Update prefix sum:
s = 5 + 2 = 7
- Copy previous best:
f[3] = f[2] = 1
- Check if
s - target = 7 - 3 = 4
exists ind
: NO - No new subarray found
- Update hash table:
d[7] = 3
- State:
d = {0: 0, 3: 1, 5: 2, 7: 3}
,f = [0, 1, 1, 1, ∞, ∞]
Iteration 4 (i=4, element=4):
- Update prefix sum:
s = 7 + 4 = 11
- Copy previous best:
f[4] = f[3] = 1
- Check if
s - target = 11 - 3 = 8
exists ind
: NO - No new subarray found
- Update hash table:
d[11] = 4
- State:
d = {0: 0, 3: 1, 5: 2, 7: 3, 11: 4}
,f = [0, 1, 1, 1, 1, ∞]
Iteration 5 (i=5, element=3):
- Update prefix sum:
s = 11 + 3 = 14
- Copy previous best:
f[5] = f[4] = 1
- Check if
s - target = 14 - 3 = 11
exists ind
: YES, at positionj = 4
- Subarray found: from position 4 to 5, length =
5 - 4 = 1
- Update
f[5] = min(1, 1) = 1
- Update answer:
ans = min(1, f[4] + 1) = min(1, 1 + 1) = min(1, 2)
- Wait,
ans
was previously1
, but that was when we only had one subarray - Actually,
ans = min(∞, f[4] + (5-4)) = min(∞, 1 + 1) = 2
- This gives us two subarrays: one with length 1 found before position 4, and current subarray [3] with length 1
- Update hash table:
d[14] = 5
- Final state:
ans = 2
Result: The minimum sum of lengths is 2, coming from subarrays [3]
at index 0 (length 1) and [3]
at index 4 (length 1).
Solution Implementation
1class Solution:
2 def minSumOfLengths(self, arr: List[int], target: int) -> int:
3 # Dictionary to store cumulative sum -> index mapping
4 # Key: cumulative sum, Value: index (1-indexed)
5 cumsum_to_index = {0: 0}
6
7 cumulative_sum = 0
8 array_length = len(arr)
9
10 # min_length[i] stores the minimum length of a subarray ending at or before index i
11 # that sums to target (initialized to infinity)
12 min_length = [float('inf')] * (array_length + 1)
13
14 # Track the minimum sum of two subarray lengths
15 min_total_length = float('inf')
16
17 # Iterate through array with 1-based indexing
18 for current_index, current_value in enumerate(arr, 1):
19 cumulative_sum += current_value
20
21 # Carry forward the minimum length from previous position
22 min_length[current_index] = min_length[current_index - 1]
23
24 # Check if we can form a subarray ending at current position that sums to target
25 # We need cumulative_sum - target to exist in our dictionary
26 required_sum = cumulative_sum - target
27
28 if required_sum in cumsum_to_index:
29 # Found a valid subarray from start_index to current_index
30 start_index = cumsum_to_index[required_sum]
31 current_subarray_length = current_index - start_index
32
33 # Update minimum length for subarrays ending at or before current position
34 min_length[current_index] = min(min_length[current_index], current_subarray_length)
35
36 # Try to combine with best subarray ending before start_index
37 # min_length[start_index] gives us the best subarray before current one
38 combined_length = min_length[start_index] + current_subarray_length
39 min_total_length = min(min_total_length, combined_length)
40
41 # Store current cumulative sum and its index
42 cumsum_to_index[cumulative_sum] = current_index
43
44 # Return -1 if no valid pair found, otherwise return minimum total length
45 return -1 if min_total_length > array_length else min_total_length
46
1class Solution {
2 public int minSumOfLengths(int[] arr, int target) {
3 // Map to store prefix sum -> index mapping
4 // Key: prefix sum, Value: index (1-based)
5 Map<Integer, Integer> prefixSumToIndex = new HashMap<>();
6 prefixSumToIndex.put(0, 0); // Initialize with sum 0 at index 0
7
8 int n = arr.length;
9
10 // dp[i] stores the minimum length of a subarray with sum = target
11 // ending at or before index i (1-based indexing)
12 int[] minLengthUpToIndex = new int[n + 1];
13
14 // Large value representing infinity for comparison
15 final int INFINITY = 1 << 30;
16 minLengthUpToIndex[0] = INFINITY; // No valid subarray before index 0
17
18 int prefixSum = 0;
19 int minTotalLength = INFINITY;
20
21 // Iterate through the array (using 1-based indexing)
22 for (int i = 1; i <= n; ++i) {
23 int currentValue = arr[i - 1]; // Convert to 0-based array index
24 prefixSum += currentValue;
25
26 // Initially, inherit the minimum length from previous position
27 minLengthUpToIndex[i] = minLengthUpToIndex[i - 1];
28
29 // Check if we can form a subarray ending at current position with sum = target
30 // We need prefixSum[j] = prefixSum[i] - target, where j < i
31 if (prefixSumToIndex.containsKey(prefixSum - target)) {
32 int startIndex = prefixSumToIndex.get(prefixSum - target);
33 int currentSubarrayLength = i - startIndex;
34
35 // Update the minimum length of subarray ending at or before index i
36 minLengthUpToIndex[i] = Math.min(minLengthUpToIndex[i], currentSubarrayLength);
37
38 // Try to combine with the best subarray before startIndex
39 // to form two non-overlapping subarrays
40 minTotalLength = Math.min(minTotalLength, minLengthUpToIndex[startIndex] + currentSubarrayLength);
41 }
42
43 // Store current prefix sum and its index
44 prefixSumToIndex.put(prefixSum, i);
45 }
46
47 // If no valid pair of subarrays found, return -1
48 return minTotalLength > n ? -1 : minTotalLength;
49 }
50}
51
1class Solution {
2public:
3 int minSumOfLengths(vector<int>& arr, int target) {
4 // Map to store cumulative sum -> index mapping
5 // Key: cumulative sum, Value: index (1-based)
6 unordered_map<int, int> prefixSumToIndex;
7 prefixSumToIndex[0] = 0; // Initialize with sum 0 at index 0
8
9 int cumulativeSum = 0;
10 int n = arr.size();
11
12 // minLength[i] stores the minimum length of a valid subarray
13 // ending at or before index i (1-based indexing)
14 int minLength[n + 1];
15 const int INF = 1 << 30; // Large value representing infinity
16 minLength[0] = INF; // No valid subarray before index 0
17
18 int result = INF;
19
20 // Iterate through the array with 1-based indexing
21 for (int i = 1; i <= n; ++i) {
22 int currentValue = arr[i - 1]; // Convert to 0-based array index
23 cumulativeSum += currentValue;
24
25 // Initialize current minimum length with previous minimum
26 minLength[i] = minLength[i - 1];
27
28 // Check if there exists a subarray ending at current position with sum = target
29 // We need: cumulativeSum - previousSum = target
30 // So: previousSum = cumulativeSum - target
31 if (prefixSumToIndex.count(cumulativeSum - target)) {
32 int startIndex = prefixSumToIndex[cumulativeSum - target];
33 int currentSubarrayLength = i - startIndex;
34
35 // Update minimum length of valid subarray ending at or before index i
36 minLength[i] = min(minLength[i], currentSubarrayLength);
37
38 // Try to form two non-overlapping subarrays:
39 // 1. Best subarray ending at or before startIndex
40 // 2. Current subarray from startIndex+1 to i
41 result = min(result, minLength[startIndex] + currentSubarrayLength);
42 }
43
44 // Store current cumulative sum and its index
45 prefixSumToIndex[cumulativeSum] = i;
46 }
47
48 // If no valid pair of subarrays found, return -1
49 return result > n ? -1 : result;
50 }
51};
52
1/**
2 * Finds the minimum sum of lengths of two non-overlapping subarrays that each sum to target.
3 * If no such subarrays exist, returns -1.
4 *
5 * @param arr - The input array of numbers
6 * @param target - The target sum for each subarray
7 * @returns The minimum sum of lengths of two valid subarrays, or -1 if not possible
8 */
9function minSumOfLengths(arr: number[], target: number): number {
10 // Map to store prefix sum -> index mapping
11 // Key: prefix sum value, Value: index position
12 const prefixSumToIndex = new Map<number, number>();
13 prefixSumToIndex.set(0, 0);
14
15 let currentPrefixSum = 0;
16 const arrayLength = arr.length;
17
18 // Array to store minimum length of valid subarray ending at or before index i
19 // minLengthUpTo[i] represents the minimum length of a valid subarray in arr[0...i-1]
20 const minLengthUpTo: number[] = Array(arrayLength + 1);
21
22 const INFINITY = 1 << 30; // Large value representing infinity
23 minLengthUpTo[0] = INFINITY;
24
25 let minSumOfTwoLengths = INFINITY;
26
27 // Iterate through the array to find valid subarrays
28 for (let i = 1; i <= arrayLength; ++i) {
29 const currentValue = arr[i - 1];
30 currentPrefixSum += currentValue;
31
32 // Inherit the minimum length from previous position
33 minLengthUpTo[i] = minLengthUpTo[i - 1];
34
35 // Check if there exists a subarray ending at current position with sum = target
36 // We need prefixSum[j] such that prefixSum[i] - prefixSum[j] = target
37 // Therefore, prefixSum[j] = prefixSum[i] - target
38 if (prefixSumToIndex.has(currentPrefixSum - target)) {
39 const startIndex = prefixSumToIndex.get(currentPrefixSum - target)!;
40 const currentSubarrayLength = i - startIndex;
41
42 // Update minimum length of valid subarray up to current position
43 minLengthUpTo[i] = Math.min(minLengthUpTo[i], currentSubarrayLength);
44
45 // Try to combine with the best subarray before startIndex
46 // to get minimum sum of two non-overlapping subarrays
47 minSumOfTwoLengths = Math.min(
48 minSumOfTwoLengths,
49 minLengthUpTo[startIndex] + currentSubarrayLength
50 );
51 }
52
53 // Store current prefix sum and its index
54 prefixSumToIndex.set(currentPrefixSum, i);
55 }
56
57 // If no valid pair of subarrays found, return -1
58 return minSumOfTwoLengths > arrayLength ? -1 : minSumOfTwoLengths;
59}
60
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the input array. The algorithm makes a single pass through the array with a for loop that iterates through each element exactly once. Within each iteration, all operations are constant time: dictionary lookups (s - target in d
), dictionary insertions (d[s] = i
), array access and updates (f[i]
), and arithmetic operations. Therefore, the overall time complexity is linear.
The space complexity is O(n)
, where n
is the length of the input array. The algorithm uses:
- A dictionary
d
that can store up ton + 1
key-value pairs (one for each possible prefix sum including the initial 0) - An array
f
of sizen + 1
to store the minimum subarray lengths - A few scalar variables (
s
,n
,ans
) which useO(1)
space
The dominant space usage comes from the dictionary and array, both of which scale linearly with the input size, resulting in O(n)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling the Case Where Multiple Indices Have the Same Prefix Sum
The Pitfall:
The current implementation overwrites the previous index when the same prefix sum appears multiple times: cumsum_to_index[cumulative_sum] = current_index
. This can lead to incorrect results because we might miss valid subarrays that could form better combinations.
Example Scenario:
Consider arr = [1, 1, 1, 2, 2, 2, 4, 4]
with target = 4
:
- At index 3, cumulative sum = 3
- At index 6, cumulative sum = 9
- At index 7, cumulative sum = 13
- At index 8, cumulative sum = 17
If we later encounter a position where we need cumulative sum 9 to form a subarray with sum 4, we'll only have the latest index stored, potentially missing a shorter subarray.
Solution: While the current approach works correctly for this specific problem (because we always want the most recent occurrence to minimize subarray length), it's important to understand why:
- When we encounter
cumulative_sum - target
in the dictionary, we want the rightmost (most recent) occurrence - This gives us the shortest possible subarray ending at the current position
- The algorithm naturally handles this by overwriting with the latest index
2. Confusion Between 0-Based and 1-Based Indexing
The Pitfall:
The code uses 1-based indexing for the iteration (enumerate(arr, 1)
) but the array min_length
and dictionary operations mix both conventions, which can be confusing and error-prone when modifying the code.
Solution: Stick to one indexing convention throughout:
def minSumOfLengths(self, arr: List[int], target: int) -> int:
# Use 0-based indexing consistently
cumsum_to_index = {0: -1} # -1 represents before the array starts
cumulative_sum = 0
n = len(arr)
# min_length[i] represents minimum length found up to index i (inclusive)
min_length = [float('inf')] * n
min_total_length = float('inf')
for i in range(n):
cumulative_sum += arr[i]
# Carry forward from previous index
if i > 0:
min_length[i] = min_length[i - 1]
required_sum = cumulative_sum - target
if required_sum in cumsum_to_index:
start_index = cumsum_to_index[required_sum]
current_subarray_length = i - start_index
min_length[i] = min(min_length[i], current_subarray_length)
# Combine with best subarray before start_index
if start_index >= 0:
combined_length = min_length[start_index] + current_subarray_length
else:
combined_length = current_subarray_length
min_total_length = min(min_total_length, combined_length)
cumsum_to_index[cumulative_sum] = i
return -1 if min_total_length > n else min_total_length
3. Not Properly Initializing the First Element of min_length Array
The Pitfall:
If the very first element equals the target, we need to ensure min_length[0]
or min_length[1]
(depending on indexing) is properly set. The line min_length[current_index] = min_length[current_index - 1]
could propagate infinity values incorrectly.
Solution: The current implementation handles this correctly because:
- It checks for valid subarrays after copying the previous value
- It updates
min_length[current_index]
if a valid subarray is found - The initialization with infinity ensures no invalid comparisons
However, for clarity, you could explicitly handle the first element:
if current_index == 1:
min_length[current_index] = float('inf')
else:
min_length[current_index] = min_length[current_index - 1]
What data structure does Breadth-first search typically uses to store intermediate states?
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
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Want a Structured Path to Master System Design Too? Don’t Miss This!