Problem overview
The method maxSumOfThreeSubarrays receives an array nums with at least three integers and returns the largest total of three non-empty, non-overlapping subarrays, in left-to-right order.
For nums = [2, 3, -8, 7, -2, 9, -9, 7, -2, 4] the answer is 28: the subarrays [2, 3], [7, -2, 9] and [7, -2, 4] sum to 5 + 14 + 9.
Approach: remember the best start for the middle subarray
The previous lesson stores leftMaxSum and rightMaxSum, then tries every middle subarray nums[k..i] with two nested loops, for O(n²) time. This lesson removes the inner loop.
Let sum(a..b) be the sum of nums[a..b], and let prefix(i) be the sum of nums[0..i]. If the middle subarray starts at k and ends at i, its sum is prefix(i) - prefix(k - 1). The best first subarray before it is worth leftMaxSum(k - 1). So for a fixed end i, the first two parts together are worth:
prefix(i) + (leftMaxSum(k - 1) - prefix(k - 1))
Only the bracketed part depends on k. Define maxDiff as the largest value of leftMaxSum(k - 1) - prefix(k - 1) over every start k up to i. Then for each end i the best total is prefix(i) + maxDiff + rightMaxSum(i + 1), with a single constant-time step per i, and maxDiff can be updated as i moves right.
In the example, take the middle subarray ending at index 5. Here prefix(5) is 11. The best start k is 3, with leftMaxSum(2) = 5 and prefix(2) = -3, so maxDiff is 8. The first two parts are worth 11 + 8 = 19 (which is 5 + 14), and rightMaxSum(6) = 9 adds the third: 28.
Step by step
- Store the length in
val n. - Create
leftMaxSumasArray.fill(n)(0). The array is mutable even though the variable is aval. - Set
leftMaxSum(0)tonums(0). - Start
maxSumatnums(0), the best sum of a subarray ending at the current index. - Loop
ifrom 1 ton - 1. - Update
maxSumtonums(i) + scala.math.max(maxSum, 0): extend the previous subarray when its sum is positive, otherwise start fresh. - Set
leftMaxSum(i)to the larger ofleftMaxSum(i - 1)andmaxSum. - Create
rightMaxSumthe same way, withnzeros. - Set
rightMaxSum(n - 1)tonums(n - 1). - Reuse
maxSumfor the best sum of a subarray that starts at the current index, beginning atnums(n - 1). - Loop
idownwards withn - 2 to 0 by -1. - Update
maxSumwith the same rule, then setrightMaxSum(i)to the larger ofrightMaxSum(i + 1)andmaxSum. - Reset
maxSumtoInt.MinValueto hold the answer. - Create
partialSumstarting atnums(0). At the beginning of each iteration it holds the sum ofnums[0..i - 1]. - Create
maxDiffstarting atInt.MinValue. It will keep the best value ofleftMaxSum(k - 1) - prefix(k - 1)seen so far. The first loop iteration assigns it before anything reads it. - Loop
iover the possible last elements of the middle subarray:1 until n - 1. It needs a first subarray before it and a third one after it. - Add the option "the middle subarray starts at
i". SetmaxDiffto the larger of itself andleftMaxSum(i - 1) - partialSum. At this momentpartialSumstill equalsprefix(i - 1), exactly the term needed. - Add
nums(i)topartialSum. It now equalsprefix(i). - Update the answer with
partialSum + rightMaxSum(i + 1) + maxDiff. - End with
maxSum. It is the last expression, so it is the result.
The solution in Scala
class Solution {
def maxSumOfThreeSubarrays(nums: Array[Int]): Int = {
val n = nums.length
val leftMaxSum = Array.fill(n)(0)
leftMaxSum(0) = nums(0)
var maxSum = nums(0)
for (i <- 1 until n) {
maxSum = nums(i) + scala.math.max(maxSum, 0)
leftMaxSum(i) = scala.math.max(leftMaxSum(i - 1), maxSum)
}
val rightMaxSum = Array.fill(n)(0)
rightMaxSum(n - 1) = nums(n - 1)
maxSum = nums(n - 1)
for (i <- n - 2 to 0 by -1) {
maxSum = nums(i) + scala.math.max(maxSum, 0)
rightMaxSum(i) = scala.math.max(rightMaxSum(i + 1), maxSum)
}
maxSum = Int.MinValue
var partialSum = nums(0)
var maxDiff = Int.MinValue
for (i <- 1 until n - 1) {
maxDiff = scala.math.max(leftMaxSum(i - 1) - partialSum, maxDiff)
partialSum += nums(i)
maxSum = scala.math.max(partialSum + rightMaxSum(i + 1) + maxDiff, maxSum)
}
maxSum
}
}
The Scala details worth a closer look:
scala.math.maxis the same function asmath.max. The lesson spells out the full package name; both work without an import.- Order of the two updates. The line that updates
maxDiffcomes beforepartialSum += nums(i). Swapping them would subtract the prefix that includesnums(i), which is wrong. Int.MinValueas a start value. It works formaxDiffbecause the first iteration replaces it with a real value before anything adds to it. Adding toInt.MinValueor subtracting from it would wrap around silently, sinceIntis 32 bits.- Range
1 until n - 1. It stops atn - 2, sorightMaxSum(i + 1)never reads past the array. - Three phases, one
var.maxSumserves three purposes in turn, each preceded by a fresh assignment.
Tracing the example
For the example array, leftMaxSum is [2, 5, 5, 7, 7, 14, 14, 14, 14, 14] and rightMaxSum is [14, 14, 14, 14, 9, 9, 9, 9, 4, 4]. The final loop then goes through these states:
i=1 maxDiff=0 partialSum=5 total=19
i=2 maxDiff=0 partialSum=-3 total=11
i=3 maxDiff=8 partialSum=4 total=21
i=4 maxDiff=8 partialSum=2 total=19
i=5 maxDiff=8 partialSum=11 total=28
i=6 maxDiff=8 partialSum=2 total=19
i=7 maxDiff=12 partialSum=9 total=25
i=8 maxDiff=12 partialSum=7 total=23
The largest total is 28, at i = 5. Each row is one constant-time step: no inner loop is needed to choose where the middle subarray starts.
Complexity
Time: O(n). There are three loops, each doing a constant amount of work per index: one for leftMaxSum, one for rightMaxSum and one for the final answer.
Space: O(n). The two helper arrays hold n numbers each. The remaining variables are single integers.
Edge cases and common mistakes
- Exactly three numbers. The loop runs once, with
i = 1, and gives the total of the three elements. - All negative numbers.
[-1, -2, -3]returns-6, and[-5, -1, -4, -2, -8]returns-7. Starting the answer at 0 would break these. - Using the prefix after the update. If
maxDiffusespartialSumafternums(i)was added, the middle subarray would lose its first element. - Looping to
n - 1.rightMaxSum(i + 1)would be out of bounds, and no third subarray would remain anyway. - Treating
maxDiffas a sum of a subarray. It is not one; it is a bookkeeping value that is combined withpartialSumlater and can be negative.
Testing
[2, 3, -8, 7, -2, 9, -9, 7, -2, 4]gives28.[-1, -2, -3]gives-6.[-5, -1, -4, -2, -8]gives-7.
