Problem overview
The function maxSumOfThreeSubarrays receives an IntArray named nums with at least three numbers. It must choose three non-empty, non-overlapping subarrays and return the largest possible sum of all their elements. Numbers can be negative.
For [2, 3, -8, 7, -2, 9, -9, 7, -2, 4] the best choice is [2, 3], [7, -2, 9] and [7, -2, 4], which sum to 5 + 14 + 9 = 28.
Approach: precompute the best left and right subarray
Fix the middle subarray as nums[i..j]. The first subarray is the best one anywhere in nums[0..i - 1] and the third is the best one anywhere in nums[j + 1..n - 1]. A direct solution recomputes those two values for every pair (i, j) with Kadane's algorithm, which makes it cubic.
The same left value is needed for every j that goes with a given i, and the same right value is needed for many different i. So compute them once:
leftMaxSum[i]is the best subarray sum inside the prefixnums[0..i];rightMaxSum[i]is the best subarray sum inside the suffixnums[i..n - 1].
Each array costs one linear pass. After that, a middle subarray costs a constant amount of work, and trying all of them is O(n²).
The two arrays on the example
For [2, 3, -8, 7, -2, 9, -9, 7, -2, 4] the passes produce:
leftMaxSum = [2, 5, 5, 7, 7, 14, 14, 14, 14, 14]
rightMaxSum = [14, 14, 14, 14, 9, 9, 9, 9, 4, 4]
Take the middle subarray [7, -2, 9], at indices 3 to 5. The best first subarray is leftMaxSum[2], which is 5 (from [2, 3]). The best third subarray is rightMaxSum[6], which is 9 (from [7, -2, 4]). The total is 5 + 14 + 9 = 28, and no other middle subarray beats it. Each array is a running maximum: leftMaxSum never decreases from left to right, and rightMaxSum never increases.
Step by step
- Store the size as
n. - Create
leftMaxSumwithIntArray(n), and set element 0 tonums[0]: the only subarray of a one-element prefix is that element. - Keep
maxSum, the best sum of a subarray that ends at the current index, starting atnums[0]. - Walk
ifrom 1 ton - 1. First updatemaxSumtonums[i] + maxOf(maxSum, 0): the best run ending atieither extends the previous run, if its sum is positive, or starts ati. - Fill
leftMaxSum[i]with the larger ofleftMaxSum[i - 1]andmaxSum. The best subarray in the prefix either lies entirely beforeior ends ati. - Build
rightMaxSumthe same way from the other end. Set its last element tonums[n - 1], reusemaxSum(now the best run starting at the current index) and walkifromn - 2down to 0. - Reset
maxSumtoInt.MIN_VALUEto hold the final answer. It must start below any possible total, because the answer can be negative. - Loop over the middle subarray's first index
ifrom 1 ton - 1, and readsum1fromleftMaxSum[i - 1]. - Loop over its last index
jfromiton - 2. Addnums[j]tosum2, which was reset to 0 for thisi, and readsum3fromrightMaxSum[j + 1]. - Keep the largest
sum1 + sum2 + sum3and return it after the loops.
The solution in Kotlin
class Solution {
fun maxSumOfThreeSubarrays(nums: IntArray): Int {
val n = nums.size
val leftMaxSum = IntArray(n)
leftMaxSum[0] = nums[0]
var maxSum = nums[0]
for (i in 1 until n) {
maxSum = nums[i] + maxOf(maxSum, 0)
leftMaxSum[i] = maxOf(leftMaxSum[i - 1], maxSum)
}
val rightMaxSum = IntArray(n)
rightMaxSum[n - 1] = nums[n - 1]
maxSum = nums[n - 1]
for (i in n - 2 downTo 0) {
maxSum = nums[i] + maxOf(maxSum, 0)
rightMaxSum[i] = maxOf(rightMaxSum[i + 1], maxSum)
}
maxSum = Int.MIN_VALUE
for (i in 1 until n) {
val sum1 = leftMaxSum[i - 1]
var sum2 = 0
for (j in i until n - 1) {
sum2 += nums[j]
val sum3 = rightMaxSum[j + 1]
maxSum = maxOf(maxSum, sum1 + sum2 + sum3)
}
}
return maxSum
}
}
Kotlin details worth knowing:
IntArray(n)starts filled with zeros. Every slot is overwritten here anyway, element 0 directly and the rest in the loop.valon an array still allows changing its elements.val leftMaxSummeans the variable can't point to another array.leftMaxSum[i] = ...changes the contents and is allowed.- One
var, two jobs.maxSumis declared once and reassigned for the right pass and for the final answer. That works because the earlier values are no longer needed once they are stored in the arrays. n - 2 downTo 0counts down and includes both ends. A plain0 until n - 1would count up. If you writen - 2..0by mistake, the range is empty because..only counts up, and the right array would stay mostly zeros.- Ranges that can be empty are fine. When
iisn - 1,i until n - 1holds nothing and the inner loop is skipped. Intwraps on overflow. Sums past about 2.1 billion wrap silently; useLongfor such data.
Complexity
Time: O(n²). The two precomputing passes are O(n). The double loop tries about n²/2 middle subarrays at O(1) each, so it dominates.
Space: O(n). The two arrays hold n integers each. This beats the previous approach, where each slice was copied again for every middle subarray.
Edge cases and common mistakes
- All negative numbers. For
[-1, -2, -3]the answer is -6. Initialising the answer to 0 would return 0. - Exactly three numbers.
[1, 2, 3]gives 6, with one element in each part. - Prefix means "inside", not "ending at".
leftMaxSum[i]is a running maximum, so it never decreases asigrows. Storing only the best run ending atiwould miss a better subarray that ended earlier. - Index shifts. The first subarray must end before the middle one starts, so use
leftMaxSum[i - 1], notleftMaxSum[i]. For the third, userightMaxSum[j + 1]. - Letting
jreachn - 1. ThenrightMaxSum[j + 1]reads past the end and throws anArrayIndexOutOfBoundsException. - Not resetting
sum2for eachi, which would mix sums from different middle subarrays.
Testing
[2, 3, -8, 7, -2, 9, -9, 7, -2, 4]gives28.[1, 2, 3, 4, 5]gives15, using the whole array split into three parts.[-1, -2, -3, -4]gives-6.[5, -100, 5, -100, 5]gives15.[-5, -1, -5, -1, -5]gives-7.