Maximum Number of Non-Overlapping Subarrays With Sum Equals Target
MedPrompt
Given an array nums and an integer target, return the maximum number of non-empty non-overlapping subarrays such that the sum of values in each subarray is equal to target.
Example 1:
Input: nums = [1,1,1,1,1], target = 2
Output: 2
Explanation: There are 2 non-overlapping subarrays [1,1,1,1,1] with sum equals to target(2).Example 2:
Input: nums = [-1,3,5,1,4,2,-9], target = 6
Output: 2
Explanation: There are 3 subarrays with sum equal to 6.
([5,1], [4,2], [3,5,1,4,2,-9]) but only the first 2 are non-overlapping.
Constraints:
1 <= nums.length <= 105-104 <= nums[i] <= 1040 <= target <= 106
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses dynamic programming to solve the problem. We define dp[i] as the maximum number of non-overlapping subarrays with a sum equal to target that can be found in the suffix of the array starting from index i. The final answer will be dp[0], which is computed by iterating backward through the array and considering all possible subarrays.
Algorithm
- Create a
dparray of sizen + 1, wherenis the length ofnums. Initialize all its values to 0. - Iterate
ifromn - 1down to0:- First, assume we don't start a subarray at
i. The result would be the same as for the subarray starting ati+1. So, setdp[i] = dp[i + 1]. - Initialize
current_sum = 0. - Then, try to find a subarray starting at
i. Iteratejfromiton - 1:- Add
nums[j]tocurrent_sum. - If
current_sumequalstarget, we've found a valid subarraynums[i...j]. - This contributes 1 to the count, plus the maximum number of subarrays we can find in the remaining part of the array, which starts at index
j + 1. This is given bydp[j + 1]. - Update
dp[i]to be the maximum of its current value and1 + dp[j + 1].
- Add
- First, assume we don't start a subarray at
- The final answer is
dp[0], which represents the maximum number of subarrays for the entire arraynums[0...n-1].
Walkthrough
We build a dp array of size n+1, where n is the length of nums. dp[i] will store the result for the subarray nums[i:]. We initialize dp[n] to 0, as there are no subarrays in an empty array.
We iterate backward from i = n-1 down to 0. For each index i, we have two choices:
- Don't start a subarray at index
i: In this case, the number of subarrays is the same as the number we can find starting fromi+1, which isdp[i+1]. We setdp[i] = dp[i+1]as a baseline. - Start a subarray at index
i: We iterate with a second pointerjfromiton-1, calculating the sum ofnums[i...j]. If this sum equalstarget, we have found a valid subarray. This gives us 1 subarray plus the maximum number of subarrays we can find in the rest of the array, which starts from indexj+1. This value is1 + dp[j+1]. We then updatedp[i]with the maximum value found among all such valid subarrays starting ati.
After the loops complete, dp[0] contains the maximum number of non-overlapping subarrays for the entire array.
class Solution { public int maxNonOverlapping(int[] nums, int target) { int n = nums.length; int[] dp = new int[n + 1]; for (int i = n - 1; i >= 0; i--) { // Option 1: Skip the element at index i dp[i] = dp[i + 1]; long currentSum = 0; // Option 2: Find a subarray starting at i for (int j = i; j < n; j++) { currentSum += nums[j]; if (currentSum == target) { // Found a subarray nums[i...j]. // The result is 1 + max subarrays from index j+1. dp[i] = Math.max(dp[i], 1 + dp[j + 1]); } } } return dp[0]; }}Complexity
Time
O(N^2), where N is the length of `nums`. The nested loops (one for `i` and one for `j`) result in a quadratic time complexity.
Space
O(N), where N is the length of `nums`, for the `dp` array.
Trade-offs
Pros
It is a straightforward and systematic way to explore all possibilities, guaranteeing an optimal solution.
The logic is relatively easy to understand for those familiar with dynamic programming.
Cons
The O(N^2) time complexity is inefficient for large inputs as specified by the constraints (N up to 10^5) and will likely result in a 'Time Limit Exceeded' error.
A more efficient approach is to use a greedy strategy combined with the prefix sum technique. The core idea is that whenever we find a subarray that sums to the target, we should commit to it and restart our search from the next element. This is because taking the earliest-ending subarray maximizes the length of the remaining array, thus maximizing the opportunity to find more non-overlapping subarrays. We can efficiently find these subarrays in linear time using a hash set to store prefix sums.
Algorithm
- Initialize
count = 0andprefix_sum = 0. - Create a
HashSetcalledseen_sumsand add0to it. This initial0is crucial for finding subarrays that start at the beginning of a search segment. - Iterate through each number
numin thenumsarray:- Update
prefix_sumby addingnumto it. - Check if
prefix_sum - targetexists inseen_sums.- If it exists, it means we have found a subarray ending at the current position with the desired sum.
- Increment
count. - To ensure the next subarray is non-overlapping, we must reset our search. Set
prefix_sum = 0, clearseen_sums, and add0back to it.
- Increment
- If it does not exist, it means no subarray ending at the current position sums to
target.- Add the current
prefix_sumtoseen_sumsto be used for future checks.
- Add the current
- If it exists, it means we have found a subarray ending at the current position with the desired sum.
- Update
- After iterating through the entire array, return
count.
Walkthrough
We iterate through the array from left to right, maintaining a current_prefix_sum. We also use a HashSet to store the prefix sums encountered since the last found subarray (or from the beginning).
At each index i, we update current_prefix_sum. To check if a subarray ending at i has a sum of target, we need to see if current_prefix_sum - target exists in our set of previously seen prefix sums. This is based on the property that sum(nums[j+1...i]) = prefix_sum[i] - prefix_sum[j]. If we want this sum to be target, we need to find a j such that prefix_sum[j] = current_prefix_sum - target.
If we find such a prefix sum in our set, we've found a valid subarray. Following the greedy strategy, we increment our count of non-overlapping subarrays. Then, to ensure the next subarray we find is non-overlapping, we 'reset' our search. This is done by clearing the HashSet and re-initializing it with just 0 (to handle subarrays that start from the next element). We also effectively reset the current_prefix_sum to 0 relative to the new starting point.
import java.util.HashSet;import java.util.Set; class Solution { public int maxNonOverlapping(int[] nums, int target) { // Use a set to store prefix sums encountered in the current search window. Set<Long> seenSums = new HashSet<>(); // Add 0 to handle subarrays that start from the beginning of a search segment. seenSums.add(0L); int count = 0; long currentSum = 0; for (int num : nums) { currentSum += num; if (seenSums.contains(currentSum - target)) { // Found a subarray with the target sum. count++; // Reset for the next non-overlapping subarray search. currentSum = 0; seenSums.clear(); seenSums.add(0L); } else { // Add the current prefix sum to the set for future checks. seenSums.add(currentSum); } } return count; }}Complexity
Time
O(N), where N is the length of `nums`. We iterate through the array only once, and `HashSet` lookups and insertions take, on average, O(1) time.
Space
O(N) in the worst case, where N is the length of `nums`. The `HashSet` could store up to N distinct prefix sums if no target-sum subarray is found and all prefix sums are unique.
Trade-offs
Pros
Highly efficient with O(N) time complexity, making it suitable for large inputs.
The implementation is concise and elegantly combines the prefix sum technique with a greedy choice.
Cons
The correctness of the greedy strategy is not immediately obvious and relies on the insight that taking the shortest possible subarray is always optimal.
The space complexity is O(N) in the worst-case, which might be a concern for extremely memory-constrained environments, although it's generally acceptable.
Solutions
Solution
class Solution { public int maxNonOverlapping ( int [] nums , int target ) { int ans = 0 , n = nums . length ; for ( int i = 0 ; i < n ; ++ i ) { Set < Integer > vis = new HashSet <>(); int s = 0 ; vis . add ( 0 ); while ( i < n ) { s += nums [ i ]; if ( vis . contains ( s - target )) { ++ ans ; break ; } ++ i ; vis . add ( s ); } } return ans ; } }class Solution {public: int maxNonOverlapping(vector<int> &nums, int target) { int ans = 0, n = nums.size(); for (int i = 0; i < n; ++i) { unordered_set<int> vis{{0}}; int s = 0; while (i < n) { s += nums[i]; if (vis.count(s - target)) { ++ans; break; } ++i; vis.insert(s); } } return ans; }};class Solution: def maxNonOverlapping(self, nums: List[int], target: int) -> int: ans = 0 i, n = 0, len(nums) while i < n: s = 0 vis = {0} while i < n: s += nums[i] if s - target in vis: ans += 1 break i += 1 vis . add(s) i += 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.