Maximum Sum of Two Non-Overlapping Subarrays
MedPrompt
Given an integer array nums and two integers firstLen and secondLen, return the maximum sum of elements in two non-overlapping subarrays with lengths firstLen and secondLen.
The array with length firstLen could occur before or after the array with length secondLen, but they have to be non-overlapping.
A subarray is a contiguous part of an array.
Example 1:
Input: nums = [0,6,5,2,2,5,1,9,4], firstLen = 1, secondLen = 2
Output: 20
Explanation: One choice of subarrays is [9] with length 1, and [6,5] with length 2.Example 2:
Input: nums = [3,8,1,3,2,1,8,9,0], firstLen = 3, secondLen = 2
Output: 29
Explanation: One choice of subarrays is [3,8,1] with length 3, and [8,9] with length 2.Example 3:
Input: nums = [2,1,5,6,0,9,5,0,3,8], firstLen = 4, secondLen = 3
Output: 31
Explanation: One choice of subarrays is [5,6,0,9] with length 4, and [0,3,8] with length 3.
Constraints:
1 <= firstLen, secondLen <= 10002 <= firstLen + secondLen <= 1000firstLen + secondLen <= nums.length <= 10000 <= nums[i] <= 1000
Approaches
3 approaches with complexity analysis and trade-offs.
This approach improves upon the brute force by avoiding redundant calculations. It breaks the problem into two cases: the first subarray appears before the second, and vice-versa. It then uses dynamic programming ideas to solve each case in linear time.
Algorithm
- Create a helper function
solve(prefix, L, M). - Inside
solve:- Initialize
maxLsum = 0andmaxTotal = 0. - Iterate
ifromL + Mton:- Calculate sum of L-subarray ending at
i - M - 1:sumL = prefix[i - M] - prefix[i - M - L]. - Update
maxLsum = max(maxLsum, sumL). - Calculate sum of M-subarray ending at
i - 1:sumM = prefix[i] - prefix[i - M]. - Update
maxTotal = max(maxTotal, maxLsum + sumM).
- Calculate sum of L-subarray ending at
- Return
maxTotal.
- Initialize
- In the main function:
- Calculate prefix sums for
nums. - Call
res1 = solve(prefix, firstLen, secondLen). - Call
res2 = solve(prefix, secondLen, firstLen). - Return
max(res1, res2).
- Calculate prefix sums for
Walkthrough
The core idea is to solve two separate, symmetric problems and take the maximum of their results:
- Max sum with a
firstLensubarray appearing before asecondLensubarray. - Max sum with a
secondLensubarray appearing before afirstLensubarray.
Let's focus on case 1. We can write a helper function solve(L, M) for this. We iterate through the array, considering each possible position for the M-length subarray. For each position, we need the maximum sum of an L-length subarray that appears entirely before it. To do this efficiently, as we iterate from left to right with a split point, we can keep track of the maximum L-length subarray sum seen so far to the left of the split point. We first compute a prefix sum array to get subarray sums in O(1). The solve(L, M) function works as follows:
- Initialize
maxL(max sum of an L-subarray) andmaxTotal. - Iterate
ifromL + Mton. The indexirepresents the end of the combined window. - The
M-length subarray ends ati-1. Its sum issumM. - The
L-length subarray must end at or beforei - M - 1. We updatemaxLby considering the newL-length subarray that just became available (the one ending ati - M - 1). - We then update
maxTotalwithmaxL + sumM.
The final result is max(solve(firstLen, secondLen), solve(secondLen, firstLen)).
class Solution { public int maxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) { int n = nums.length; int[] prefix = new int[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } return Math.max( findMaxSum(prefix, firstLen, secondLen), findMaxSum(prefix, secondLen, firstLen) ); } private int findMaxSum(int[] prefix, int L, int M) { int n = prefix.length - 1; int maxL = 0; int maxTotal = 0; // Window of L+M, M is the second subarray for (int i = L + M; i <= n; i++) { // Max L-sum subarray in the first part [0, i-M-1] int sumL = prefix[i - M] - prefix[i - M - L]; maxL = Math.max(maxL, sumL); // M-sum subarray in the second part [i-M, i-1] int sumM = prefix[i] - prefix[i - M]; maxTotal = Math.max(maxTotal, maxL + sumM); } return maxTotal; }}Complexity
Time
O(N), where N is the length of `nums`. We make a single pass to compute prefix sums, and then two more passes inside the helper function.
Space
O(N) to store the prefix sum array.
Trade-offs
Pros
Much more efficient than the brute-force approach with a linear time complexity.
Cons
Requires extra space for the prefix sum array.
This is the most efficient approach, achieving linear time complexity with constant extra space. It builds upon the logic of the previous approach but eliminates the need for a prefix sum array by using sliding windows to calculate subarray sums on the fly.
Algorithm
- Create a helper function
solve(nums, L, M). - Inside
solve:- Initialize
sumL = 0for the firstLelements. - Initialize
sumM = 0for the nextMelements. maxL = sumL.maxTotal = sumL + sumM.- Iterate
ifromL + Mton - 1:- Update
sumLby sliding the window:sumL = sumL - nums[i - L - M] + nums[i - M]. - Update
maxL = max(maxL, sumL). - Update
sumMby sliding the window:sumM = sumM - nums[i - M] + nums[i]. - Update
maxTotal = max(maxTotal, maxL + sumM).
- Update
- Return
maxTotal.
- Initialize
- In the main function:
- Call
res1 = solve(nums, firstLen, secondLen). - Call
res2 = solve(nums, secondLen, firstLen). - Return
max(res1, res2).
- Call
Walkthrough
Similar to the DP approach, we solve for two cases: firstLen subarray before secondLen, and vice-versa. The final answer is the maximum of the two. Let's analyze the helper function solve(L, M) which finds the max sum with an L-length subarray before an M-length one, but this time without a prefix sum array. We maintain three key values: sumL (sum of the current L-length window), sumM (sum of the current M-length window), and maxL (the maximum sumL found so far). We initialize by calculating the sum of the first possible L-subarray (nums[0...L-1]) and the first possible M-subarray (nums[L...L+M-1]). This gives us our initial maxL and maxTotal. Then, we iterate from i = L + M to n-1. In each step, we slide both windows one position to the right. The sumL window is updated by subtracting the element that slides out and adding the new element that slides in. We then update maxL with the new sumL. The sumM window is also updated similarly. We then calculate the potential new max total sum: maxL + sumM, and update our overall maxTotal if it's larger. By doing this for both (firstLen, secondLen) and (secondLen, firstLen), we find the global maximum.
class Solution { public int maxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) { return Math.max( solve(nums, firstLen, secondLen), solve(nums, secondLen, firstLen) ); } private int solve(int[] nums, int L, int M) { int n = nums.length; int sumL = 0; for (int i = 0; i < L; i++) { sumL += nums[i]; } int sumM = 0; for (int i = L; i < L + M; i++) { sumM += nums[i]; } int maxL = sumL; int maxTotal = sumL + sumM; for (int i = L + M; i < n; i++) { // Slide window for L sumL += nums[i - M] - nums[i - M - L]; maxL = Math.max(maxL, sumL); // Slide window for M sumM += nums[i] - nums[i - M]; maxTotal = Math.max(maxTotal, maxL + sumM); } return maxTotal; }}Complexity
Time
O(N), where N is the length of `nums`. The helper function is called twice, and each call involves a single pass through the array.
Space
O(1). We only use a few variables to store the running sums and maximums, independent of the input size.
Trade-offs
Pros
Optimal solution with linear time and constant space complexity.
Cons
The logic can be slightly more complex to grasp initially compared to the prefix sum approach.
This approach iterates through all possible pairs of non-overlapping subarrays. To make the calculation of subarray sums efficient, it first precomputes a prefix sum array.
Algorithm
- Get the length of the array,
n. - Create a prefix sum array
prefixof sizen + 1. - Populate
prefix:prefix[i] = prefix[i-1] + nums[i-1]. - Initialize
maxSum = 0. - Loop
ifrom0ton - firstLen:- Calculate
sum1 = prefix[i + firstLen] - prefix[i]. - Loop
jfrom0ton - secondLen:- Check for non-overlapping:
if (i + firstLen <= j || j + secondLen <= i). - If non-overlapping, calculate
sum2 = prefix[j + secondLen] - prefix[j]. - Update
maxSum = max(maxSum, sum1 + sum2).
- Check for non-overlapping:
- Calculate
- Return
maxSum.
Walkthrough
First, we compute a prefix sum array, prefix, where prefix[i] is the sum of all elements from nums[0] to nums[i-1]. This allows us to calculate the sum of any subarray nums[j...k] in O(1) time as prefix[k+1] - prefix[j]. We then use two nested loops to consider every possible starting position for the first subarray (length firstLen) and the second subarray (length secondLen). Let the first subarray start at index i and the second at index j. Inside the loops, we check if the two subarrays are non-overlapping. The first subarray occupies indices [i, i + firstLen - 1] and the second occupies [j, j + secondLen - 1]. They are non-overlapping if the first one ends before the second one starts (i + firstLen <= j) or if the second one ends before the first one starts (j + secondLen <= i). If they are non-overlapping, we calculate their combined sum using the prefix sum array and update a maxSum variable if this sum is greater than the current maximum. After checking all pairs, maxSum will hold the result.
class Solution { public int maxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) { int n = nums.length; int[] prefix = new int[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } int maxSum = 0; for (int i = 0; i <= n - firstLen; i++) { int sum1 = prefix[i + firstLen] - prefix[i]; for (int j = 0; j <= n - secondLen; j++) { // Check for non-overlapping if (i + firstLen <= j || j + secondLen <= i) { int sum2 = prefix[j + secondLen] - prefix[j]; maxSum = Math.max(maxSum, sum1 + sum2); } } } return maxSum; }}Complexity
Time
O(N^2), where N is the length of `nums`. We have two nested loops that iterate up to N. The prefix sum calculation takes `O(N)`.
Space
O(N) for the prefix sum array.
Trade-offs
Pros
Simple to understand and implement.
Correctly explores all possibilities.
Cons
Inefficient due to the nested loops, leading to a quadratic time complexity which might be too slow for larger inputs.
Solutions
Solution
class Solution {public int maxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) { int n = nums.length; int[] s = new int[n + 1]; for (int i = 0; i < n; ++i) { s[i + 1] = s[i] + nums[i]; } int ans = 0; for (int i = firstLen, t = 0; i + secondLen - 1 < n; ++i) { t = Math.max(t, s[i] - s[i - firstLen]); ans = Math.max(ans, t + s[i + secondLen] - s[i]); } for (int i = secondLen, t = 0; i + firstLen - 1 < n; ++i) { t = Math.max(t, s[i] - s[i - secondLen]); ans = Math.max(ans, t + s[i + firstLen] - s[i]); } return ans; }}class Solution {public: int maxSumTwoNoOverlap(vector<int> &nums, int firstLen, int secondLen) { int n = nums.size(); vector<int> s(n + 1); for (int i = 0; i < n; ++i) { s[i + 1] = s[i] + nums[i]; } int ans = 0; for (int i = firstLen, t = 0; i + secondLen - 1 < n; ++i) { t = max(t, s[i] - s[i - firstLen]); ans = max(ans, t + s[i + secondLen] - s[i]); } for (int i = secondLen, t = 0; i + firstLen - 1 < n; ++i) { t = max(t, s[i] - s[i - secondLen]); ans = max(ans, t + s[i + firstLen] - s[i]); } return ans; }};class Solution: def maxSumTwoNoOverlap(self, nums: List[int], firstLen: int, secondLen: int) -> int: n = len(nums) s = list(accumulate(nums, initial=0)) ans = t = 0 i = firstLen while i + secondLen - 1 < n: t = max(t, s[i] - s[i - firstLen]) ans = max(ans, t + s[i + secondLen] - s[i]) i += 1 t = 0 i = secondLen while i + firstLen - 1 < n: t = max(t, s[i] - s[i - secondLen]) ans = max(ans, t + s[i + firstLen] - s[i]) 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.