Problem overview
The function maxSumOfThreeSubarrays receives a slice nums of integers, with at least three elements, and returns the largest possible total of three non-overlapping, non-empty subarrays. The subarrays are contiguous pieces of nums, and they may leave gaps between them.
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, so the answer is 28. The elements -8 and -9 are left out as gaps.
Approach: try every middle, take the best on each side
Choosing three subarrays at once has too many combinations. Fix the middle one instead. If the middle subarray is nums[i..j], the other two must lie entirely before it and entirely after it. The first one can be any subarray of nums[:i], and the best choice is simply the maximum subarray sum in that prefix. The third is the maximum subarray sum in the suffix nums[j+1:]. The total for this middle is then sum1 + sum2 + sum3.
The maximum sum of a subarray in a slice comes from Kadane's algorithm, from the problem Maximum Sum Subarray: scan once and keep the best sum of a subarray ending at the current element. That sum either extends the previous one, if it is positive, or starts fresh at the current value.
The solution tries every middle with two loops. The outer loop picks the start i, and the inner loop extends the end j one element at a time, keeping a running sum for the middle. For each pair it runs Kadane on the prefix once per i and on the suffix once per j. This is simple and correct, but it costs O(n³). Precomputing the best prefix and suffix values would remove a factor, but this version keeps the idea as direct as possible.
Step by step
- Store
len(nums)inn, since it is used often. - Declare
maxSum, the best total so far. The answer can be negative, so it starts at-(1 << 31), the smallest 32-bit integer, instead of 0. - Loop over
ifrom 1 whilei < n. This is the first element of the middle subarray. Starting at 1 keeps the first subarray non-empty. - Compute
sum1withmaxSumSubarray(nums[:i]), the best subarray beforei. - Declare
sum2at 0: the middle subarray is empty until the inner loop adds elements. - Loop over
jfromiwhilej < n-1, the last element of the middle subarray. Stopping atn - 2leaves at least one element for the third subarray. - Add
nums[j]tosum2. - Compute
sum3withmaxSumSubarray(nums[j+1:]), the best subarray afterj. - Update
maxSumwithmax(maxSum, sum1+sum2+sum3). - Return
maxSumafter the outer loop. - Write
maxSumSubarrayabove it, taking a[]intand returning anint: the Kadane scan. - Declare
result, the best sum so far, starting atnums[0]and not 0, because all the numbers may be negative. - Declare
maxSum, the best sum of a subarray ending at the current element, starting at 0. This is a different variable from the one in the main function. - Loop over the values with
range, naming each oneval. - Update the running sum:
valplus the larger ofmaxSumand 0. - Update
resultwith the larger ofresultandmaxSum. - Return
result.
The solution in Go
func maxSumSubarray(nums []int) int {
result := nums[0]
maxSum := 0
for _, val := range nums {
maxSum = val + max(maxSum, 0)
result = max(result, maxSum)
}
return result
}
func maxSumOfThreeSubarrays(nums []int) int {
n := len(nums)
maxSum := -(1 << 31)
for i := 1; i < n; i++ {
sum1 := maxSumSubarray(nums[:i])
sum2 := 0
for j := i; j < n-1; j++ {
sum2 += nums[j]
sum3 := maxSumSubarray(nums[j+1:])
maxSum = max(maxSum, sum1+sum2+sum3)
}
}
return maxSum
}
Go details worth a closer look:
- Slicing does not copy.
nums[:i]andnums[j+1:]are views of the same backing array, so passing them tomaxSumSubarraycosts nothing extra in memory. The helper only reads them. - Built-in
max. Since Go 1.21,maxis a built-in function for ordered types, so nomathimport is needed. - The constant
-(1 << 31)is an untyped constant, and:=givesmaxSumthe typeint, which is 64 bits here, so the value is -2,147,483,648. The idea is only "below any possible total". A total below that would need numbers of very large magnitude. - Two variables named
maxSum. One is local to the helper, the other tomaxSumOfThreeSubarrays. They never interact. - The inner loop may not run. When
i == n-1, the conditionj < n-1fails at once, sosum3never receives an empty slice, which would makenums[0]in the helper panic.
Complexity
Time: O(n³). The two loops try O(n²) middles, and every inner step runs maxSumSubarray on a suffix of up to n elements. The prefix scan runs once per outer step and adds only O(n²) in total.
Space: O(1). The slices are views of the input and the helper keeps a few integers.
Edge cases and common mistakes
- Exactly three elements.
[1, 2, 3]gives 6. Onlyi = 1,j = 1is possible, so each subarray is a single element. - All negative.
[-1, -2, -3]gives -6 and[-5, -1, -7, -2]gives -8, from-5,-1and-2. StartingmaxSumat 0 would wrongly produce 0, and so would starting the Kadaneresultat 0. - Gaps are allowed. In
[4, -10, 4, -10, 4]the answer is 12: the three 4s with the -10s skipped between them. - Empty subarrays. The loop bounds keep each part non-empty: the prefix has at least one element since
istarts at 1, the middle has one sincejstarts ati, and the suffix has one sincejstops atn - 2. - Kadane's reset. Taking
val + max(maxSum, 0)drops a negative running sum. Adding it anyway would drag later values down. - Slice bounds.
nums[j+1:]is valid even forj + 1 == nand gives an empty slice, but the loop never gets there.
Testing
[2, 3, -8, 7, -2, 9, -9, 7, -2, 4]gives 28.[1, 2, 3]gives 6.[-1, -2, -3]gives -6.[-5, -1, -7, -2]gives -8.[4, -10, 4, -10, 4]gives 12.
