Problem overview
The method maxSumOfThreeSubarrays takes an array nums with at least three integers and picks three non-empty subarrays that don't overlap and come one after another. It returns the largest possible sum of the three. Numbers can be negative, so the best answer may be negative too.
For [2, 3, -8, 7, -2, 9, -9, 7, -2, 4] the best choice is [2, 3], [7, -2, 9] and [7, -2, 4], with sums 5, 14 and 9, for a total of 28.
Approach: precompute the best left and right subarrays
Fix the middle subarray by its first index i and last index j. The first subarray must fit inside nums[0..i - 1] and the third inside nums[j + 1..n - 1], and they can be chosen separately. The slow version of this idea computes the best subarray of each prefix and suffix again for every pair (i, j), which costs O(n³) in total.
The same prefixes and suffixes come up over and over, so this lesson computes each answer once and stores it:
leftMaxSum[i]is the maximum subarray sum insidenums[0..i];rightMaxSum[i]is the maximum subarray sum insidenums[i..n - 1].
Both arrays are built in one pass each. The main search then reads from them in constant time, leaving two nested loops over the middle subarray: O(n²).
For the example, the two arrays are:
nums: 2 3 -8 7 -2 9 -9 7 -2 4
leftMaxSum: 2 5 5 7 7 14 14 14 14 14
rightMaxSum: 14 14 14 14 9 9 9 9 4 4
With the middle subarray [7, -2, 9] (indices 3 to 5), the best left part is leftMaxSum[2], which is 5, and the best right part is rightMaxSum[6], which is 9. The total is 5 + 14 + 9 = 28.
Step by step
- Store the length in
n. - Create
leftMaxSumwithnew Array(n).fill(0). The constructor makes an array ofnempty slots, andfill(0)puts a zero in each. - Set
leftMaxSum[0]tonums[0]. The only subarray of the first prefix is the first element. - Start
maxSumatnums[0]. It tracks the best sum of a subarray that ends at the current index. - Loop
ifrom 1 ton - 1. - Update
maxSumtonums[i] + Math.max(maxSum, 0). The best subarray ending atiextends the previous one only when that one has a positive sum; otherwise it starts fresh atnums[i]. - Set
leftMaxSum[i]to the larger ofleftMaxSum[i - 1]andmaxSum. The best subarray insidenums[0..i]either ends atior lies completely before it. - Create
rightMaxSumthe same way asleftMaxSum. - Set
rightMaxSum[n - 1]tonums[n - 1]. The last suffix has one subarray only. - Reuse
maxSum, now holding the best sum of a subarray that starts at the current index. Start it atnums[n - 1]. - Loop
ifromn - 2down to 0. The suffix atidepends on the one ati + 1, so the loop runs right to left. - Update
maxSumwith the same formula,nums[i] + Math.max(maxSum, 0): extend the best subarray that starts ati + 1if it is positive, or start fresh. - Set
rightMaxSum[i]to the larger ofrightMaxSum[i + 1]andmaxSum. - Reset
maxSumtoNumber.NEGATIVE_INFINITYfor the answer. It is the best total found so far, and the answer can be negative, so it must start below any real total. - Loop
ifrom 1 ton - 1, the first index of the middle subarray. Starting at 1 leaves room for a non-empty first subarray. - Read
sum1fromleftMaxSum[i - 1], the best first subarray that ends beforei. - Start
sum2at 0. It will hold the middle subarray's sum. - Loop
jfromiton - 2, the last index of the middle subarray. The upper limit leaves one element for the third subarray. - Add
nums[j]tosum2. - Read
sum3fromrightMaxSum[j + 1], the best third subarray starting afterj. - Update
maxSumwithsum1 + sum2 + sum3if it is larger. - Return
maxSum.
The solution in TypeScript
class Solution {
maxSumOfThreeSubarrays(nums: number[]): number {
let n = nums.length;
let leftMaxSum: number[] = new Array(n).fill(0);
leftMaxSum[0] = nums[0];
let maxSum = nums[0];
for (let i = 1; i < n; ++i) {
maxSum = nums[i] + Math.max(maxSum, 0);
leftMaxSum[i] = Math.max(leftMaxSum[i - 1], maxSum);
}
let rightMaxSum: number[] = new Array(n).fill(0);
rightMaxSum[n - 1] = nums[n - 1];
maxSum = nums[n - 1];
for (let i = n - 2; i >= 0; --i) {
maxSum = nums[i] + Math.max(maxSum, 0);
rightMaxSum[i] = Math.max(rightMaxSum[i + 1], maxSum);
}
maxSum = Number.NEGATIVE_INFINITY;
for (let i = 1; i < n; ++i) {
let sum1 = leftMaxSum[i - 1];
let sum2 = 0;
for (let j = i; j < n - 1; ++j) {
sum2 += nums[j];
let sum3 = rightMaxSum[j + 1];
maxSum = Math.max(maxSum, sum1 + sum2 + sum3);
}
}
return maxSum;
}
}
The TypeScript details worth knowing:
- Creating a filled array.
new Array(n)with a single number argument makes an array of that length with empty slots, and.fill(0)turns them into zeros. The annotationnumber[]tells the compiler what the array will hold; it has no effect at run time. - One variable, two jobs.
maxSumis reassigned three times: for the best subarray ending at the current index, then starting at it, then for the final answer. Because it is declared withletand has typenumberfrom its first value, all three uses are allowed. - Prefix and suffix versions of the same formula. Both passes use
nums[i] + Math.max(maxSum, 0). Only the direction changes. - Doubles, not integers. Sums are plain
numbervalues, so there is no overflow to worry about for ordinary inputs; integers stay exact up to 2^53.
Complexity
Time: O(n²). Building each of the two arrays is a single pass, O(n). The final nested loops try about n²/2 pairs (i, j) and spend constant time on each one, because the left and right bests are plain array reads.
Space: O(n). The two arrays hold n numbers each.
Edge cases and common mistakes
- Negative totals. For
[-5, -1, -3, -2]the answer is-6. Initializing the answer with 0 would return 0, which is not the sum of any three subarrays. - Minimum size. For
[1, 2, 3]the only valid choice is three single elements, and the code returns6. - Starting a maximum at 0.
leftMaxSum[0]and the firstmaxSumstart atnums[0]because an array of negative values has no non-negative subarray sum. - Off-by-one on the sides. The first subarray ends before
i, so it readsleftMaxSum[i - 1]. The third starts afterj, so it readsrightMaxSum[j + 1]. UsingleftMaxSum[i]would let the first subarray overlap the middle one. - Treating the arrays as exact-length bests.
leftMaxSum[i]is the best sum anywhere inside the prefix, not the best sum of a subarray ending ati. The runningmaxSumholds the latter.
Testing
[2, 3, -8, 7, -2, 9, -9, 7, -2, 4]gives28.[-5, -1, -3, -2]gives-6.[1, 2, 3]gives6.[-1, -2, -3]gives-6.