Problem overview
The method MaxSumSubarray receives an int[] with positive and negative numbers and returns the largest sum of any continuous, non-empty subarray. For [-2, -5, 6, -2, -3, 1, 5, -6] the answer is 7, from the subarray [6, -2, -3, 1, 5]. The statement allows O(n) time and at most O(n) extra space.
Approach: a subarray sum is a difference of prefix sums
The prefix sum at position i is the sum of all elements up to and including i. The sum of a subarray from l to r equals the prefix sum at r minus the prefix sum just before l. The empty prefix has the sum 0.
So the largest subarray sum ending at element r is the prefix sum at r minus the smallest earlier prefix sum, the empty prefix included. Taking the best over all r gives the answer. Instead of storing all prefix sums in an array, it is enough to keep the running one, partialSum, and the smallest one seen so far, minSum.
The trace on the example lists the three values after each element: partialSum, minSum and result. They are -2, -2 and -2 for the first element, then -7, -7 and -2, then -1, -7 and 6, then -3, -7 and 6, then -6, -7 and 6, then -5, -7 and 6, then 0, -7 and 7, and finally -6, -7 and 7. At the seventh element, partialSum is 0 and minSum is -7, so the best subarray ending there sums to 0 - (-7) = 7. That subarray starts right after the point where the prefix sum was lowest: the elements 6, -2, -3, 1 and 5.
The statement says at most O(n) space because the idea is usually explained with a full prefix array. Keeping just two numbers brings the extra space down to O(1).
Step by step
- Declare
partialSumandminSum, both 0. The prefix before any element is empty and has the sum 0, so it's the first candidate for the smallest prefix. - Initialise
resultwithnums[0]. It has to be a sum some subarray really has. Starting at 0 would be wrong for arrays of negative numbers only. - Loop over the elements with
foreach, reading each asval. - Extend the running sum:
partialSum += val. - Update
result. The best subarray ending here has the sumpartialSum - minSum, so takeMath.Max(result, partialSum - minSum). - Update
minSumwithMath.Min(minSum, partialSum), so the next element can use this prefix as a starting point. - Return
resultafter the loop.
The order of steps 5 and 6 matters. result must be updated before minSum takes the current prefix into account. Otherwise the subarray would start after the current element and end at it, which is empty.
The solution in C#
class Solution {
public int MaxSumSubarray(int[] nums) {
int partialSum = 0, minSum = 0;
int result = nums[0];
foreach (int val in nums) {
partialSum += val;
result = Math.Max(result, partialSum - minSum);
minSum = Math.Min(minSum, partialSum);
}
return result;
}
};
The C# details to notice:
- Declaring two variables together.
int partialSum = 0, minSum = 0;declares two separateintvariables, each with its own initial value. +=adds to the variable and stores the result back in it.- Silent overflow.
intsums wrap around without an error by default. If the prefix sums could pass roughly 2.1 billion, declarepartialSum,minSumandresultaslong. - The closing
};after the class is legal. An extra semicolon after a type declaration is allowed in C#.
Complexity
Time: O(n). One pass with constant work per element.
Space: O(1). Three integer variables. A version that stores the whole prefix array would use O(n), which the statement allows.
Edge cases and common mistakes
- All negative numbers.
[-3, -1, -2]gives -1. The best subarray is the single element -1, and the method finds it at the second position: the prefix sum is -4 and the smallest earlier prefix is -3, so the difference is -1. - A single element.
[4]gives 4, from the prefix 4 minus the empty prefix 0. - All positive numbers.
[1, 2, 3]gives 6, becauseminSumstays 0 and the full prefix sum is the biggest. - Starting
minSumtoo high. Initialising it withnums[0]or a large value would exclude the empty prefix, so subarrays that begin at index 0 couldn't be measured. - Updating
minSumfirst. It would let a subarray end where it started and subtract the current prefix from itself, which gives 0 and is wrong when every number is negative. - Empty array.
nums[0]would throwIndexOutOfRangeException. The problem assumes at least one element.
Testing
[-2, -5, 6, -2, -3, 1, 5, -6]gives 7.[-3, -1, -2]gives -1.[4]gives 4.[1, 2, 3]gives 6.