Problem overview
The method maxSumOfThreeSubarrays receives an array nums of at least three integers. It must choose three non-empty subarrays that don't overlap and appear in order, and return the largest possible sum of the three. Values may be negative.
For [2, 3, -8, 7, -2, 9, -9, 7, -2, 4] the answer is 28, from [2, 3], [7, -2, 9] and [7, -2, 4].
Approach: remove the loop over the middle subarray's start
The previous idea fixes the middle subarray by its first index and last index. The best first subarray then comes from an array of prefix maxima, and the best third from an array of suffix maxima, so each pair of boundaries costs constant time. Two nested loops still try every pair, which takes O(n²).
This lesson keeps the last index of the middle subarray as the only loop and drops the loop over its start. Write P[k] for the sum of nums[0..k - 1], so P[0] is 0. If the middle subarray starts at k and ends at i, its sum is P[i + 1] - P[k]. The total for that choice is
leftMaxSum[k - 1] + (P[i + 1] - P[k]) + rightMaxSum[i + 1]
Group the parts that depend on k: (leftMaxSum[k - 1] - P[k]). For a fixed i, the best start k is the one that makes this part largest, among the starts 1 to i. That maximum is maxDiff, and it can be kept as a running maximum while i moves right: each new i adds one more possible start and nothing else changes.
In the example, the best total is found at i = 5. The best start up to then is k = 3, where leftMaxSum[2] - P[3] is 5 - (-3) = 8. Then P[6] is 11, rightMaxSum[6] is 9, and 11 + 8 + 9 = 28.
Step by step
- Store the length in
n, typed asnumber. - Create
leftMaxSumas an array ofnzeros. Entryiwill hold the best subarray sum insidenums[0..i]. - Set
leftMaxSum[0]tonums[0]. The first prefix has one subarray only. - Start
maxSumatnums[0], the best sum of a subarray ending at the current index. - Loop
ifrom 1 ton - 1. - Update
maxSumtonums[i] + Math.max(maxSum, 0): extend the earlier subarray if its sum is positive, otherwise start atnums[i]. - Set
leftMaxSum[i]to the larger ofleftMaxSum[i - 1]andmaxSum. The best subarray inside the prefix either ends atior lies before it. - Create
rightMaxSumas another array of zeros. - Set
rightMaxSum[n - 1]tonums[n - 1]. - Reuse
maxSum, now for the best subarray starting at the current index, beginning withnums[n - 1]. - Loop
ifromn - 2down to 0. - Update
maxSumwith the same formula as before,nums[i] + Math.max(maxSum, 0), this time extending the subarray that starts ati + 1. - Set
rightMaxSum[i]to the larger ofrightMaxSum[i + 1]andmaxSum. - Reset
maxSumtoNumber.NEGATIVE_INFINITYto hold the answer. The answer can be negative, so it must start below every real total. - Create
partialSumstarting atnums[0]. At the start of each iteration below it equalsP[i], the sum ofnums[0..i - 1]. - Create
maxDiffstarting atNumber.NEGATIVE_INFINITY. It is the largest value ofleftMaxSum[k - 1] - P[k]seen so far. - Loop
ifrom 1 ton - 2, the last index of the middle subarray. It needs a non-empty subarray before it and after it. - Update
maxDiffwithleftMaxSum[i - 1] - partialSum. That addsk = ias a possible start. It happens beforepartialSumchanges, whilepartialSumis stillP[i]. - Add
nums[i]topartialSum, which now equalsP[i + 1]. - Update
maxSumwithpartialSum + rightMaxSum[i + 1] + maxDiff, the best first subarray, plus the middle one ending ati, plus the best third. - Return
maxSum.
The solution in TypeScript
class Solution {
maxSumOfThreeSubarrays(nums: number[]): number {
let n: number = nums.length;
let leftMaxSum: number[] = new Array(n).fill(0);
leftMaxSum[0] = nums[0];
let maxSum: number = 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;
let partialSum: number = nums[0];
let maxDiff: number = Number.NEGATIVE_INFINITY;
for (let i = 1; i < n - 1; ++i) {
maxDiff = Math.max(leftMaxSum[i - 1] - partialSum, maxDiff);
partialSum += nums[i];
maxSum = Math.max(partialSum + rightMaxSum[i + 1] + maxDiff, maxSum);
}
return maxSum;
}
}
The TypeScript details worth knowing:
- Annotations are optional here.
let n: number = nums.lengthsays what the compiler would already infer. The annotations are erased when TypeScript compiles to JavaScript, so they document the code and catch mistakes at compile time, and they do nothing at run time. Number.NEGATIVE_INFINITYas a start value. Any real number is larger than it, so the firstMath.maxreplaces it. StartingmaxDiffat 0 would be wrong, because the differenceleftMaxSum[k - 1] - P[k]can be negative for every start.- Order inside the loop. The three statements must stay in order. Updating
maxDiffusespartialSumbeforenums[i]is added; the last line uses it after. - Numbers are doubles. Differences and sums of ordinary inputs are exact, and there is no integer overflow to handle.
Complexity
Time: O(n). There are three loops, each running at most n times with constant work in the body.
Space: O(n). The two arrays leftMaxSum and rightMaxSum hold n numbers each. The remaining variables are single numbers.
Edge cases and common mistakes
- Exactly three elements. For
[1, 2, 3]the loop runs once, withi = 1, and returns6. - All negative. For
[-1, -2, -3]the answer is-6. A starting value of 0 formaxSumormaxDiffwould hide it. - Wrong loop range. Running
iup ton - 1would readrightMaxSum[n], which isundefinedin JavaScript, and the arithmetic would produceNaN. - Updating
partialSumtoo early. Ifnums[i]were added beforemaxDiffis updated, the difference would subtractP[i + 1]instead ofP[i], and the middle subarray would lose its first element. - Middle subarray of one element. The start
kmay equali, becausemaxDiffis updated with the currentibefore it is used. Updating it after the last line would force the middle subarray to have at least two elements.
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.