Problem overview
The function maxSumSubarray receives an IntArray called nums that may hold positive and negative numbers. It must return the largest sum of a contiguous block of elements. The block can't be empty, and it can be as short as a single element or as long as the whole array.
In the example, nums = [-2, -5, 6, -2, -3, 1, 5, -6] and the answer is 7, from the block [6, -2, -3, 1, 5]: 6 - 2 - 3 + 1 + 5 = 7. The statement asks for O(n) time and at most O(n) extra space. This version of the lesson, the prefix sums approach, actually needs only a few variables.
Approach: best block ending here = running total minus the lowest earlier total
The slow way tries every start and every end and adds up each block, which costs O(n²) at best and O(n³) if each block is summed from scratch. Prefix sums remove most of that work.
Call the sum of the first j elements the prefix sum at j. The sum of a block that starts after position i and ends at position j is the prefix sum at j minus the prefix sum at i. So for a fixed end j, the best block ending there uses the smallest earlier prefix sum. Subtracting the smallest number leaves the largest difference.
That gives a one-pass algorithm. Walk through the array keeping the running total (partialSum) and the smallest prefix sum seen before the current element (minSum). At each element, partialSum - minSum is the best block that ends at that element, and the answer is the maximum of these values.
The empty prefix counts as an earlier prefix with sum 0. That is why minSum starts at 0: it allows a block that begins at index 0.
Here is the example, element by element:
| element | partialSum | minSum before it | best block ending here |
|---|---|---|---|
| -2 | -2 | 0 | -2 |
| -5 | -7 | -2 | -5 |
| 6 | -1 | -7 | 6 |
| -2 | -3 | -7 | 4 |
| -3 | -6 | -7 | 1 |
| 1 | -5 | -7 | 2 |
| 5 | 0 | -7 | 7 |
| -6 | -6 | -7 | 1 |
The largest value in the last column is 7, which matches the expected answer. The smallest prefix sum, -7, was reached after the second element, so the winning block starts right after it: [6, -2, -3, 1, 5].
Step by step
- Declare
partialSumandminSumasvars set to 0. Both change in the loop, and 0 stands for the empty prefix. - Declare
resultwithvar, starting atnums[0]. A one-element block is a valid answer, sonums[0]is a value the result can never fall below. Starting at 0 would be wrong when every number is negative, because the answer must be a real, non-empty block. - Loop over the elements with
for (value in nums). The namevalis a keyword in Kotlin, which is why the loop variable is calledvalue. - Add
valuetopartialSum. It now holds the sum of everything up to and including this element. - Update
resultwithmaxOf(result, partialSum - minSum), the best block ending at this element. - Update
minSumwithminOf(minSum, partialSum), so the next element can start a block right after this one. - Return
resultonce the loop is done.
The solution in Kotlin
class Solution {
fun maxSumSubarray(nums: IntArray): Int {
var partialSum = 0
var minSum = 0
var result = nums[0]
for (value in nums) {
partialSum += value
result = maxOf(result, partialSum - minSum)
minSum = minOf(minSum, partialSum)
}
return result
}
}
The Kotlin details worth a closer look:
varversusval. Avalcan be assigned once. All three accumulators are reassigned inside the loop, so they arevars.maxOfandminOf. These are standard-library functions that return the larger or smaller of their arguments. They replace anifstatement and keep each update on one line.- The order of the two updates matters.
resultis updated beforeminSum. IfminSumwere updated first, it could become equal to the currentpartialSum, and the difference would be 0, which stands for an empty block. With[-3, -1, -2]that would return 0 instead of -1. Intis 32 bits. Sums beyond about 2.1 billion wrap around silently. If the inputs could be that large,partialSumand the result would need to beLong.- Iterating an
IntArray.for (value in nums)reads the elements directly and needs no index. It also avoids the off-by-one mistakes of a manual index loop.
Complexity
Time: O(n). The loop runs once per element, and each iteration does a constant amount of work: one addition, one maxOf and one minOf.
Space: O(1). The function keeps three Int variables and never builds an array of prefix sums. That is within the O(n) limit in the statement.
Edge cases and common mistakes
- All negative numbers.
[-3, -1, -2]returns-1, the best single element. Initializingresultwith 0 would return 0, which is the sum of no block at all. - A single element.
[4]returns4. - An empty array.
nums[0]throws anArrayIndexOutOfBoundsException. The lesson's input always has at least one number. - Forgetting the empty prefix. If
minSumstarted atnums[0]or at some large value, a block beginning at index 0 could never be chosen. - Swapping the two updates. As described above, this lets an empty block win when all numbers are negative.
- Overflow. Very large values can exceed the range of
Int, and the sums wrap around without an error.
Testing
[-2, -5, 6, -2, -3, 1, 5, -6]gives7.[-3, -1, -2]gives-1.[4]gives4.