Problem overview
The method MaxSumSubarray receives an int[] named nums that may contain positive and negative numbers. It returns the largest sum that any continuous run of elements, a subarray, can have.
For nums = [-2, -5, 6, -2, -3, 1, 5, -6] the best subarray is [6, -2, -3, 1, 5], with sum 7. No other run does better. The statement allows O(n²) time and O(1) extra space.
Approach: try every subarray
A subarray is fixed by its first index i and its last index j, with i <= j. A brute force simply tries every such pair and keeps the best sum.
Summing each subarray from scratch would add a third loop and cost O(n³). The trick is to notice that for a fixed i, moving j one step to the right only adds one element. So keep a running sum: reset it to 0 when i changes, and add nums[j] each time j moves. The sum is then always the sum of nums[i..j], and getting it costs O(1).
A prefix-sum array would also give each subarray sum in constant time, but it needs O(n) extra space. The running sum doesn't.
For i = 2 in the example, j runs from 2 to 7 and the sums are 6, 4, 1, 2, 7, 1. The largest, 7, is the answer for that start index, and it is also the best overall.
The whole table for the example
This shows what the running sum holds for each start index i in [-2, -5, 6, -2, -3, 1, 5, -6], as j goes from i to the end, and the best sum of each row:
i=0: -2 -7 -1 -3 -6 -5 0 -6 best 0
i=1: -5 1 -1 -4 -3 2 -4 best 2
i=2: 6 4 1 2 7 1 best 7
i=3: -2 -5 -4 1 -5 best 1
i=4: -3 -2 3 -3 best 3
i=5: 1 6 0 best 6
i=6: 5 -1 best 5
i=7: -6 best -6
Each row is built by adding one more element to the previous entry, so no row ever re-adds elements it has already summed. The overall maximum is the largest row maximum, 7, found in row 2. maxSum changes only when a new best appears, so it ends at 7.
Step by step
- Create
maxSum, starting atnums[0]. Starting at 0 would be a mistake: if every number is negative, the true answer is negative, and 0 would beat it.nums[0]is itself a subarray, so it is a safe starting value. - Loop over the first index
ifrom 0 tonums.Length - 1. - Declare
suminside that loop and set it to 0. Every new start index begins with an empty subarray, and the variable isn't needed outside the loop. - Loop over the last index
jfromito the end, inside the first loop. Starting atimakes the one-element subarraynums[i]the first one tried. - Add
nums[j]tosum. This extends the subarray by one element. - Update
maxSumin one line withMath.Max, which returns the larger of its two arguments. - Return
maxSumafter the outer loop has covered every pair.
The solution in C#
class Solution {
public int MaxSumSubarray(int[] nums) {
int maxSum = nums[0];
for (int i = 0; i < nums.Length; ++i) {
int sum = 0;
for (int j = i; j < nums.Length; ++j) {
sum += nums[j];
maxSum = Math.Max(maxSum, sum);
}
}
return maxSum;
}
};
The C# details worth noting:
nums.Length. Arrays expose their size as theLengthproperty, with no parentheses. (List<T>usesCountinstead.)Math.Max. It lives in theSystemnamespace, so code outside the lesson's editor needsusing System;at the top of the file. It has overloads for all the numeric types; with twointarguments it returns anint.- Scope of
sum. A variable declared inside a loop body exists only within that body. Each iteration of the outer loop creates a freshsumset to 0, which is exactly the reset the algorithm needs. ++iin the loop header. Pre-increment and post-increment behave the same here, because the value of the expression isn't used.- Overflow wraps silently. In default C# code, adding past
int.MaxValuedoesn't throw. The sum wraps around to a negative number. Inputs with huge values would need alongforsumandmaxSum. The lesson's tests stay withinint. - The trailing semicolon. The
;after the class's closing brace is legal in C#, and it does nothing.
Complexity
Time: O(n²). The outer loop runs n times. For start index i the inner loop runs n - i times, so the total is n + (n-1) + … + 1, which is n(n+1)/2 and belongs to O(n²). Each inner step is constant work.
Space: O(1). Just maxSum, sum, i and j. This lesson's brute force is the baseline; later lessons on the same problem improve on the time.
Edge cases and common mistakes
- All negative numbers.
[-3, -1, -2]returns-1, the best single element. A subarray must contain at least one element, so the empty sum of 0 is not allowed. - One element.
[4]returns4. - All positive numbers.
[1, 2, 3]returns6, the whole array. - Forgetting to reset
sum. Ifsumis declared outside the outer loop, it carries the old total into the next start index, and the sums no longer describenums[i..j]. - Starting
jat 0 ori + 1. Starting at 0 adds elements beforei. Starting ati + 1never tries the one-element subarraynums[i], which breaks the all-negative case. - Empty array.
nums[0]throws anIndexOutOfRangeException. The problem assumes at least one element. - Not a subsequence. The elements must be adjacent. In
[5, -9, 6]the answer is 6, not 11, since 5 and 6 can't be joined without the -9.
Testing
[-2, -5, 6, -2, -3, 1, 5, -6]gives7.[-3, -1, -2]gives-1.[4]gives4.[1, 2, 3]gives6.[5, -9, 6]gives6.