Problem overview
The function maxSumSubarray receives a slice of integers, nums, that may contain both positive and negative values. 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 answer is 7, from the run [6, -2, -3, 1, 5]: 6 - 2 - 3 + 1 + 5 = 7. The statement sets a budget of O(n²) time and O(1) extra space, which is the budget of a brute-force search done carefully.
Approach
A subarray is fully described by its first index i and its last index j, with i <= j. So the brute force is to try every such pair and keep the best sum. There are about n²/2 pairs.
The expensive part would be adding up the elements for each pair, which costs up to O(n) and gives O(n³) in total. The way around it: for a fixed i, the subarray for j + 1 is the subarray for j with one more element on the right. So the sum doesn't need recomputing. Keep a running sum, reset it to 0 each time i moves, and add nums[j] as j advances. Every pair then costs O(1), and the whole search is O(n²) without an extra array of partial sums.
Other lessons on this problem take the time down further. This one stays with the quadratic search.
Step by step
- Declare
maxSumasnums[0]. The best sum can be negative (if every element is negative), so starting at 0 would wrongly report an empty subarray.nums[0]is itself a subarray, so it is a safe starting point. - Loop over the first index
ifrom 0 tolen(nums) - 1. - Declare
sum := 0inside that loop. Each newistarts from an empty subarray, and the variable isn't needed outside the loop. - Loop over the last index
jfromitolen(nums) - 1. Starting atiallows one-element subarrays. - Add
nums[j]tosum. Nowsumequals the sum ofnums[i..j]. - Update
maxSumin one line withmax: it keeps the larger of the old best and the new sum. - Return
maxSumafter both loops have finished.
The solution in Go
func maxSumSubarray(nums []int) int {
maxSum := nums[0]
for i := 0; i < len(nums); i++ {
sum := 0
for j := i; j < len(nums); j++ {
sum += nums[j]
maxSum = max(maxSum, sum)
}
}
return maxSum
}
The Go details worth a closer look:
- Built-in
max. Since Go 1.21,maxandminare built-in functions that take two or more values of the same ordered type. No import is needed. In older Go versions you would write theifyourself. - Scope of
sum. A variable declared with:=inside the outer loop's body exists only for that iteration, so it starts at 0 again every time.sumis not a Go keyword or built-in, so it is a fine variable name. foris the only loop. The classic three-part formfor init; condition; post { }does the job of C'sfor, and Go has no parentheses around the three parts.- Integer size.
intis 64 bits on a 64-bit machine, so the running sums of ordinary test data don't overflow. If they did, Go would wrap silently instead of reporting an error.
Complexity
Time: O(n²). For each of the n values of i, the inner loop runs n - i times, which adds up to n(n + 1)/2 iterations, each doing constant work. Space: O(1). The function keeps three integers and two loop counters, whatever the size of nums.
Edge cases and common mistakes
- All negative numbers.
[-3, -1, -2]gives-1, the best single element. InitialisingmaxSumto 0 would return 0, a sum that no subarray has. - A single element.
[4]gives4. The loops run once andmaxSumis unchanged. - All positive numbers.
[1, 2, 3]gives6, the whole slice. - Not resetting
sum. Ifsumwere declared outside the outer loop, sums from earlier starting points would carry over and the results would describe subarrays that don't exist. - Starting
jat 0. The inner loop must start ati, otherwise it adds elements to the left of the subarray's start. - Checking only the final sum. The best subarray can end before the end of the slice, so
maxSumhas to be updated for everyj. In[5, -10, 5]the best sum is 5, not the total of 0. - An empty slice.
nums[0]would panic with "index out of range". The lesson's inputs are never empty.
Testing
[-2, -5, 6, -2, -3, 1, 5, -6]gives7.[-3, -1, -2]gives-1.[4]gives4.[1, 2, 3]gives6.[5, -10, 5]gives5.