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 return the largest possible total of their sums. A subarray is a run of neighbouring elements.
In the example, [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. The total is 28. Elements -8 and -9 are left out because including them would only lower the sum.
Approach: fix the middle subarray, let Kadane do the sides
Three non-overlapping subarrays always come in left-to-right order: a first one, a second one in the middle, and a third one. Once the middle subarray nums[i...j] is fixed, the other two are independent of each other:
- the first one is the best subarray that lies entirely in
nums[0..<i]; - the third one is the best subarray that lies entirely in
nums[(j + 1)..<n].
The best subarray inside a given array is the classic maximum subarray problem, solved in one pass by Kadane's algorithm. So the plan is: try every possible middle subarray, ask Kadane for the best left and right parts, add the three sums and keep the maximum.
This is the slowest of the approaches the course covers for this problem. Later lessons precompute the best subarray on every prefix and suffix, which removes the repeated Kadane calls. Here the goal is a correct solution built from a function you already know.
Step by step
- Store
nums.countinn, withlet, since the loops use it a lot and it never changes. - Declare
maxSumwithvar, starting atInt.min. The answer can be negative, so starting at 0 would be wrong. For[-1, -2, -3]the answer is-6. - Loop over
ifrom 1 ton - 1, the first index of the middle subarray. It starts at 1 so that the first subarraynums[0..<i]has at least one element. The last value,n - 1, leaves no room for a third subarray, but the inner loop is empty then and nothing happens. - Compute
sum1as the best subarray sum ofnums[0..<i], by callingmaxSumSubarray. It is alet, because it doesn't change in this iteration. The slice is wrapped inArray(...), since the function takes an[Int]. - Start
sum2at 0 inside the loop. It is the running sum of the middle subarray for thisi. - Loop over
jfromiton - 2, the last index of the middle subarray. The upper limit leaves at least one element for the third subarray. - Add
nums[j]tosum2. Each step ofjextends the middle subarray by one element, so there is no need to sum it again. - Compute
sum3as the best subarray sum ofnums[(j + 1)..<n], again wrapped inArray(...). - Update
maxSumwith the larger of its value andsum1 + sum2 + sum3. - Return
maxSumafter the outer loop. - Write
maxSumSubarrayabove the main method: Kadane's algorithm.resultis the best sum so far and starts atnums[0], not 0, because all the numbers may be negative.maxSumis the best sum of a subarray ending at the current element. For eachval, that subarray either extends the previous one, when the previous sum is positive, or starts again atval, somaxSum = val + max(maxSum, 0). Thenresulttakes the larger of itself andmaxSum.
The solution in Swift
class Solution {
func maxSumSubarray(_ nums: [Int]) -> Int {
var result = nums[0]
var maxSum = 0
for val in nums {
maxSum = val + max(maxSum, 0)
result = max(result, maxSum)
}
return result
}
func maxSumOfThreeSubarrays(_ nums: [Int]) -> Int {
let n = nums.count
var maxSum = Int.min
for i in 1..<n {
let sum1 = maxSumSubarray(Array(nums[0..<i]))
var sum2 = 0
for j in i..<(n - 1) {
sum2 += nums[j]
let sum3 = maxSumSubarray(Array(nums[(j + 1)..<n]))
maxSum = max(maxSum, sum1 + sum2 + sum3)
}
}
return maxSum
}
}
Swift details worth knowing:
- Slices are not arrays.
nums[0..<i]has the typeArraySlice<Int>. It shares storage withnumsand keeps the original index numbers: for[10, 20, 30, 40], the slicenums[1..<3]prints as[20, 30], itsstartIndexis 1, andslice[1]is20.Array(...)turns it into a new array indexed from 0, which is whatmaxSumSubarrayexpects, and it is what makesnums[0]there the first element. - Half-open ranges.
j + 1..<nexcludesn, sonums[(j + 1)..<n]goes up to the last element. The parentheses aroundj + 1make the range readable. A range whose lower bound is greater than its upper bound crashes the program, which is why the loops are written so that the lower bound never exceeds the upper bound. - An empty inner loop is fine. When
iisn - 1, the rangei..<(n - 1)is empty (both ends aren - 1) and the body doesn't run. for val in numsgoes over the elements directly, with no index needed in Kadane's loop.- Methods call each other freely. Inside the class,
maxSumSubarray(...)needs noself.prefix.
Complexity
Time: O(n³). The two loops visit about n²/2 pairs (i, j). For each pair, building the right slice and running Kadane on it costs O(n). The left part is recomputed once per i, which adds only O(n²) in total.
Space: O(n). Each Array(...) call copies a slice, and the copy can be as long as the input.
The repeated Kadane calls are wasteful, because many of them work on the same suffix. Storing the best subarray sum for every prefix and every suffix once makes each lookup O(1).
Edge cases and common mistakes
- All negative numbers. For
[-1, -2, -3]each subarray must be one element, and the answer is-6. Kadane'sresultstarting atnums[0], andmaxSumstarting atInt.min, both handle this. Starting either at 0 would return a wrong value. - Exactly three numbers. The only choice is one element per subarray, so the answer is the total of the array. The loops allow it:
itakes the values 1 and 2, andjtakes only the value 1 fori = 1. - Empty subarrays. Starting
iat 0, or lettingjreachn - 1, would pass an empty slice tomaxSumSubarray, wherenums[0]fails because the array has no elements. - Forgetting
Array(...). The function takes[Int], so passing the slice directly is a compile error. - Counting an element twice. The first slice ends before
iand the third starts afterj, andnums[i...j]belongs to the middle. A boundary off by one makes subarrays overlap. - Skipping elements between subarrays is allowed. The subarrays don't have to touch. The sides are the best subarrays inside their parts, wherever they sit.
Testing
[2, 3, -8, 7, -2, 9, -9, 7, -2, 4]gives28.[-1, -2, -3]gives-6.[-5, -1, -7, -2]gives-8.[1, 2, 3, 4]gives10.[5, -10, 5, -10, 5]gives15.
