Problem overview
The method maxSumSubarray receives an array of integers, nums, which may include negative values. It returns the largest sum of any continuous subarray: a block of neighbouring elements, with at least one element.
In the example, [-2, -5, 6, -2, -3, 1, 5, -6], the best block is [6, -2, -3, 1, 5], with sum 7. The statement asks for O(n) time and O(1) extra space. This technique is known as Kadane's algorithm.
Approach: the best sum ending at each element
Checking every subarray works but takes O(n²) or O(n³) time. The faster idea is to ask a smaller question at each position: what is the largest sum of a subarray that ends at this element? Call it maxSum. The overall answer is the largest maxSum over all positions, since the best subarray has to end somewhere.
The recurrence is short. A subarray ending at the current element val either extends the best subarray ending at the previous element, or starts fresh at val. Extending only helps when the previous best sum is positive; if it is negative, carrying it along makes things worse. So:
maxSum = val + max(previous maxSum, 0)
Trace the example. The values of maxSum after each element are -2, -5, 6, 4, 1, 2, 7 and 1. At -5, the previous sum is negative, so the block restarts. At 6 it restarts again. Then it carries on: 6 - 2 = 4, 4 - 3 = 1, 1 + 1 = 2, 2 + 5 = 7. The largest value seen is 7.
Step by step
- Initialize
resultwithnums[0]. The answer can be negative, so starting at 0 would be wrong: for[-3, -1, -2]it would report 0, which isn't the sum of any subarray.nums[0]is a subarray by itself, so it is a safe starting point. - Start
maxSumat 0. Before any element there is an empty prefix, and the formulaval + Math.max(maxSum, 0)treats 0 and "no previous block" the same way. - Loop over the elements with a variable
val. - Update
maxSumwith the recurrenceval + Math.max(maxSum, 0): extend the previous block, or start a new one if that block's sum is negative. - Update
resultwithMath.max(result, maxSum), in case this block is the best so far. - Return
resultonce the loop is done.
The solution in TypeScript
class Solution {
maxSumSubarray(nums: number[]): number {
let result = nums[0];
let maxSum = 0;
for (let val of nums) {
maxSum = val + Math.max(maxSum, 0);
result = Math.max(result, maxSum);
}
return result;
}
}
Details worth knowing:
for (let val of nums)loops over the values, not the indices.const valwould also work, sincevalis never reassigned inside the loop.Math.maxaccepts any number of arguments and returns the largest. Here it makes the "extend or restart" choice without anif.- No overflow. TypeScript's
numberis a double. Sums stay exact up to 2^53, and nothing wraps around the way a 32-bit integer would in some other languages. - Types are erased. The annotations
number[]andnumberare checked by the compiler only; nothing verifies them when the JavaScript runs. - The input is not changed, unlike in the sorting lessons. The loop only reads
nums.
Complexity
Time: O(n). The loop visits each element once and does constant work.
Space: O(1). The code needs only result and maxSum, no matter how long the array is.
Edge cases and common mistakes
- All negative numbers.
[-3, -1, -2]returns -1: the best subarray is the single element -1. Initializingresultwith 0 would wrongly return 0. - A single element.
[4]returns 4. - All positive numbers.
[1, 2, 3]returns 6, the whole array. - An empty array. The statement doesn't allow it. The code would return
undefined, becausenums[0]doesn't exist, even though the declared type isnumber. - Confusing subarrays with subsequences. A subarray must be contiguous, so skipping the negative values in the middle isn't allowed. That's why
[6, -2, -3, 1, 5]is counted whole in the example. - Updating
resultbeforemaxSum. Thenresultwould use the previous element's sum, and the final element would be missed.
Testing
[-2, -5, 6, -2, -3, 1, 5, -6]gives7.[-3, -1, -2]gives-1.[4]gives4.[1, 2, 3]gives6.