Problem overview
The function max_sum_subarray takes a vector of integers, positive or negative, and returns the largest sum of a non-empty continuous subarray. For nums = [-2, -5, 6, -2, -3, 1, 5, -6] the answer is 7, from [6, -2, -3, 1, 5].
The statement allows O(n) time and up to O(n) extra space. This lesson reaches that bound with prefix sums, the running totals of the array.
Approach: a subarray sum is a difference of two prefix sums
The direct method tries every start and every end and adds the elements in between. Even with a running total that is O(n²) subarrays to look at, and it gets slow quickly as the vector grows. Prefix sums remove the inner work: once the total up to each position is known, any subarray sum is one subtraction, and the only remaining question is which start to subtract.
Let partial_sum after element j be the total of everything from index 0 to j. The sum of the subarray from index i to j is then the prefix sum at j minus the prefix sum just before i. The array 6, -2, -3, 1, 5 starts after the prefix sum -7 and ends at the prefix sum 0, and 0 - (-7) = 7.
So for a fixed end j, the best start is the one that subtracts the smallest earlier prefix sum. Scan left to right and keep two numbers: the running partial_sum and min_sum, the smallest prefix sum seen so far, including the empty prefix, which has sum 0. At each element the best subarray ending there has sum partial_sum - min_sum. The answer is the largest such value.
The trace on the example:
| val | partial_sum | min_sum before | partial_sum - min_sum | result |
|---|---|---|---|---|
| -2 | -2 | 0 | -2 | -2 |
| -5 | -7 | -2 | -5 | -2 |
| 6 | -1 | -7 | 6 | 6 |
| -2 | -3 | -7 | 4 | 6 |
| -3 | -6 | -7 | 1 | 6 |
| 1 | -5 | -7 | 2 | 6 |
| 5 | 0 | -7 | 7 | 7 |
| -6 | -6 | -7 | 1 | 7 |
The order inside the loop matters. result is updated before min_sum, so the subtracted prefix always comes from strictly before the current element. That keeps the subarray non-empty. If min_sum were updated first, the current prefix could subtract itself and produce an empty subarray with sum 0.
A second way to see the same algorithm is Kadane's algorithm, which tracks the best sum ending at each position directly and needs no stored prefix minimum. Both do one pass, and this version makes the connection to prefix sums explicit.
Why is it enough to remember only the smallest earlier prefix sum? Any other start would subtract a larger number and leave a smaller total for the same end. So the best start for each end is the position where the prefix sum was lowest, and nothing else about the earlier elements needs to be kept.
Step by step
- Declare
partial_sumandmin_sumaslet muti32variables, both 0. The empty prefix has sum 0, and it is a legal place for a subarray to start. - Declare
resultasnums[0]. The answer can be negative, so 0 is the wrong start. The first element is always a valid subarray sum. - Loop over
&numswith the variableval. Borrowing keeps the vector usable, andvalis a&i32. - Add the element to
partial_sum. Rust lets+=take the reference directly, so no dereference is needed. - Update
resultwithresult.max(partial_sum - min_sum). - Update
min_sumwithmin_sum.min(partial_sum), so later elements can start after this position. - Return
resultas the final expression.
The solution in Rust
impl Solution {
fn max_sum_subarray(nums: Vec<i32>) -> i32 {
let mut partial_sum: i32 = 0;
let mut min_sum: i32 = 0;
let mut result: i32 = nums[0];
for val in &nums {
partial_sum += val;
result = result.max(partial_sum - min_sum);
min_sum = min_sum.min(partial_sum);
}
result
}
}
The Rust details worth a closer look:
+=with a reference. The standard library implementsAddAssign<&i32>fori32, sopartial_sum += valcompiles wherevalis a&i32. A plainpartial_sum = partial_sum + valalso works, and writing*valis fine too.maxandminare methods. They come from theOrdtrait and take the other value as an argument:result.max(x)is the larger of the two.- Nothing is stored. Despite the name "prefix sums", this version never builds a prefix array. Only the current total and the smallest one so far matter, so the memory use is constant.
- Overflow. Under the lessons' optimized build,
i32arithmetic wraps silently, and a debug build would panic. Large inputs would call fori64. - Empty input.
nums[0]panics on an empty vector.
Complexity
Time: O(n). One pass, with a constant amount of work for each element.
Space: O(1). Three integers. The statement's allowance of O(n) would also cover storing the full prefix array, but it isn't needed.
Edge cases and common mistakes
- All negative numbers.
[-3, -1, -2]returns -1. Withresultstarting at 0, the function would report 0, which no non-empty subarray has. - A single element.
[-4]returns -4:partial_sumis -4,min_sumis still 0, and the difference is -4. - Updating
min_sumtoo early. Moving that line above theresultupdate lets an element subtract its own prefix and return an empty-subarray sum of 0 for all-negative input. - Starting
min_sumtoo high. It must start at 0, not atnums[0]ori32::MAX. Otherwise subarrays that start at index 0 are never considered.
Testing
[-2, -5, 6, -2, -3, 1, 5, -6]gives7.[-3, -1, -2]gives-1.[-4]gives-4.
