Start learning free

LESSON 220 OF 348 · PARTIAL SUMS #2

Max Sum of Three Subarrays in TypeScript: Brute Force with Kadane

Practice O(n3) Read the full explanation

Given an array nums of integers, find three non-overlapping subarrays with maximum sum.

Return the total sum of the three subarrays

Example:

Input: [2, 3, -8, 7, -2, 9, -9, 7, -2, 4]
Output: 28
Explanation: Subarrays [2, 3], [7, -2, 9] and [7, -2, 4]
             have the maximum sum of 28

 

Note:

  • Subarrays must be non-empty
  • nums contains at least three numbers
STEP 1 OF 11
    Ctrl + Enter to check

    TYPESCRIPT · PARTIAL SUMS #2

    Max Sum Of Three Subarrays in TypeScript, explained

    Problem overview

    The method maxSumOfThreeSubarrays receives an array nums of integers and must pick three non-overlapping, non-empty subarrays, one after another, so that their sums add up to the largest possible total. It returns that total. The array has at least three numbers, and the values can be negative.

    In the example, nums = [2, 3, -8, 7, -2, 9, -9, 7, -2, 4]. The subarrays [2, 3], [7, -2, 9] and [7, -2, 4] sum to 5, 14 and 9, so the answer is 28. The -8 and the -9 between them are left out, because including either would only lower the total.

    Approach: fix the middle subarray, then use the best left and right parts

    Choosing three subarrays directly means choosing six boundaries, which is a lot of loops. A smaller search comes from looking at the middle subarray. Once its first index i and last index j are fixed, the other two choices are independent of each other:

    • the first subarray must lie inside nums[0..i - 1], so the best one is the maximum subarray sum of that prefix;
    • the third subarray must lie inside nums[j + 1..n - 1], so the best one is the maximum subarray sum of that suffix.

    The answer for this middle subarray is the best prefix sum plus the middle sum plus the best suffix sum. Trying every pair i, j and keeping the largest total gives the result.

    The maximum subarray sum of a piece of the array comes from Kadane's algorithm, which this lesson reuses as a helper, maxSumSubarray. This version calls it again for every pair of middle boundaries, which is simple but slow. The other lessons on this problem precompute the best prefix and suffix sums so the helper is not repeated; this one is the brute-force starting point.

    Step by step

    1. Store the length in n, since the loops use it many times.
    2. Start maxSum at Number.NEGATIVE_INFINITY. The answer can be negative when all the numbers are, so starting at 0 would return a value that no choice of subarrays produces. Negative infinity is lower than any real total.
    3. Loop i from 1 to n - 1. It is the first index of the middle subarray. It starts at 1 so that the first subarray, nums[0..i - 1], has at least one element. At i = n - 1 there is no room for a third subarray, but the inner loop simply doesn't run.
    4. Compute sum1 as this.maxSumSubarray(nums.slice(0, i)). The helper is a method, so it is called through this. slice(0, i) copies the elements from index 0 up to, but not including, i.
    5. Start sum2 at 0 inside the outer loop. It is the sum of the middle subarray, and each new i begins a new one.
    6. Loop j from i to n - 2. It is the last index of the middle subarray. Stopping at n - 2 leaves at least one element for the third subarray.
    7. Add nums[j] to sum2. Moving j one step right extends the middle subarray by one element, so there is no need to add up the whole range again.
    8. Compute sum3 as the maximum subarray sum of nums.slice(j + 1). With only a start index, slice goes to the end of the array.
    9. Update maxSum with Math.max(maxSum, sum1 + sum2 + sum3).
    10. Return maxSum after the outer loop.
    11. Write maxSumSubarray. result is the best sum found so far and starts at nums[0], not 0, because every number might be negative. maxSum is the best sum of a subarray ending at the current element. For each val, that subarray either extends the previous one, when the previous sum is positive, or starts fresh at val. That is val + Math.max(maxSum, 0). Then result keeps the larger of itself and maxSum.

    The solution in TypeScript

    class Solution {
        maxSumSubarray(nums: number[]): number {
            let result = nums[0];
            let maxSum = 0;
            for (let val of nums) {
                maxSum = val + Math.max(maxSum, 0);
                result = Math.max(result, maxSum);
            }
            return result;
        }
    
        maxSumOfThreeSubarrays(nums: number[]): number {
            let n = nums.length;
            let maxSum = Number.NEGATIVE_INFINITY;
            for (let i = 1; i < n; i++) {
                let sum1 = this.maxSumSubarray(nums.slice(0, i));
                let sum2 = 0;
                for (let j = i; j < n - 1; j++) {
                    sum2 += nums[j];
                    let sum3 = this.maxSumSubarray(nums.slice(j + 1));
                    maxSum = Math.max(maxSum, sum1 + sum2 + sum3);
                }
            }
            return maxSum;
        }
    }
    

    The TypeScript details worth knowing:

    • slice copies. nums.slice(0, i) returns a new array and leaves nums alone. The end index is excluded, so slice(0, 3) on [2, 3, -8, 7] gives [2, 3, -8]. That copying is part of the cost of this version.
    • this is required. Inside a class, maxSumSubarray(...) on its own refers to no variable. Methods are reached through the object, so the call is this.maxSumSubarray(...).
    • Number.NEGATIVE_INFINITY is a real number. It fits the number type, compares correctly with any sum, and is replaced by the first candidate total. All numbers in TypeScript are doubles, so sums don't wrap around the way fixed-size integers would.
    • The annotations are inferred. let result = nums[0] is a number without being told. The parameter and return types on the methods are the only annotations the code needs.

    Complexity

    Time: O(n³). The two loops visit about n²/2 pairs (i, j). For each pair, slice(j + 1) and the helper both walk up to n elements, and so does the first call per i. The work per pair is O(n), so the total is cubic. Doubling the array length makes the code about eight times slower.

    Space: O(n). The slices are the only extra memory: each one holds up to n elements, and a slice is dropped once the helper returns.

    Edge cases and common mistakes

    • All numbers negative. For [-5, -1, -3, -2] the answer is -6, from the single-element subarrays [-1], [-3] and [-2]. Starting maxSum at 0 would return 0, which no valid choice produces.
    • Exactly three numbers. [1, 2, 3] returns 6: each subarray is a single element. Only i = 1 and j = 1 are possible.
    • Helper starting value. If result in maxSumSubarray started at 0, a negative-only piece would report 0 instead of its largest element.
    • Loop bounds. Letting j reach n - 1 would call the helper with an empty array, and nums[0] of an empty array is undefined, which poisons the sums with NaN.
    • Forgetting to reset sum2. It must start at 0 for every i, or the middle sum carries over from the previous start.

    Testing

    • [2, 3, -8, 7, -2, 9, -9, 7, -2, 4] gives 28.
    • [-5, -1, -3, -2] gives -6.
    • [1, 2, 3] gives 6.
    • [-1, -2, -3] gives -6.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free