Problem overview
The method maxSumSubarray receives an array of integers, nums, which may contain negative values. It returns the largest sum that any continuous subarray can have. A subarray is a run of neighbouring elements, from some index i to some index j, with i <= j. It must contain at least one element.
For nums = [-2, -5, 6, -2, -3, 1, 5, -6] the best subarray is [6, -2, -3, 1, 5], whose sum is 7. No other run of neighbours does better.
The statement allows O(n^2) time and O(1) extra space. That is the budget of a brute-force search over all subarrays, as long as each one is evaluated cheaply and no extra array is built.
Approach: try every subarray with a running sum
A subarray is defined by its two ends, so trying every subarray means trying every pair (i, j) with 0 <= i <= j < n. Two nested loops do that: the outer one picks the left end i, and the inner one picks the right end j, from i to the end of the array.
For each pair, we need the sum of nums[i..j]. Adding up the elements from scratch each time would cost another loop and make the whole thing O(n^3). Prefix sums would fix that, but they need an extra array of size n. There is a cheaper way that uses neither.
For a fixed i, moving j one step to the right just appends one element to the subarray. So keep a variable sum for the current subarray: set it to 0 when i changes, and add nums[j] each time j advances. Every pair then costs O(1), and the extra space is a couple of variables.
Alongside it, keep maxSum, the best sum seen so far, and update it after each addition.
For [3, -4, 2]: with i = 0 the running sums are 3, -1 and 1. With i = 1 they are -4 and -2. With i = 2 the only sum is 2. The largest of all of them is 3.
Step by step
- Declare
maxSumand initialize it tonums[0]. The answer can be negative, so starting at 0 would be a trap: for[-4, -1, -8]it would report 0, which is not the sum of any subarray.nums[0]is itself a subarray, so it is a safe starting value. - Loop with
iover every possible first index, from 0 tonums.length - 1. - Declare
suminside that loop and set it to 0. Declaring it there gives each start index a fresh, empty subarray, and the variable is not needed outside. - Loop with
jover every possible last index, fromitonums.length - 1. This loop sits inside the first one. Starting atimeans the shortest subarray has one element. - Add
nums[j]tosum. This extends the previous subarray by one element, sosumnow equals the sum ofnums[i..j]. - Update
maxSumin one line withMath.max(maxSum, sum), which keeps the larger of the two. - Return
maxSumonce both loops have finished, meaning every pair of indices has been tried.
The solution in TypeScript
class Solution {
maxSumSubarray(nums: number[]): number {
let maxSum = nums[0];
for (let i = 0; i < nums.length; ++i) {
let sum = 0;
for (let j = i; j < nums.length; ++j) {
sum += nums[j];
maxSum = Math.max(maxSum, sum);
}
}
return maxSum;
}
}
TypeScript details worth a look:
- Everything is inferred.
maxSumgets the typenumberfromnums[0], andsumfrom the literal 0. Only the parameter and return types are written out. letis block scoped. Thesumdeclared inside the outer loop exists only for that iteration, which is what makes the reset automatic.Math.maxtakes any number of arguments. Here it takes two and returns the larger one.- Numbers are doubles. Sums of ordinary integers are exact. Integers only start losing precision beyond 2^53, and there is no integer overflow as in some other languages.
Complexity
Time: O(n^2). For each i, the inner loop runs n - i times, so the total is n + (n - 1) + ... + 1, which is n(n + 1) / 2. The body does constant work.
Space: O(1). Only maxSum, sum, i and j are stored.
This is the brute-force baseline. Later lessons on the same problem improve the running time.
Edge cases and common mistakes
- All numbers negative. For
[-4, -1, -8]the answer is -1, the best single element. This is whymaxSumstarts atnums[0]rather than 0. - A single element. For
[5]both loops run once and the answer is 5. - Declaring
sumoutside the outer loop. Without the reset, the sum from the previous start index carries over, and subarrays are no longer measured fromi. - Starting
jat 0. Thenjcan be smaller thani, and the loop adds elements that are not part of any subarray starting ati. - Allowing an empty subarray. The answer must be a real subarray, so the empty sum of 0 is not a candidate.
- An empty array.
nums[0]isundefined, the loops never run, and the method returnsundefinedeven though its type saysnumber. The compiled code has no check for it, so a real program would testnums.lengthfirst.
Testing
[-2, -5, 6, -2, -3, 1, 5, -6]gives 7.[-4, -1, -8]gives -1.[5]gives 5.[3, -4, 2]gives 3.