Problem overview
The method maxSumSubarray receives an array of integers, nums, which may hold both positive and negative values. It returns the largest sum that any continuous run of elements can have. The run must be unbroken, so you can't skip elements in the middle.
For nums = [-2, -5, 6, -2, -3, 1, 5, -6] the answer is 7, the sum of [6, -2, -3, 1, 5]. The statement allows O(n²) time and O(1) extra space.
Approach: try every subarray, but add one element at a time
A subarray is fixed by its first index i and its last index j, with i <= j. The brute-force idea is to try every such pair and keep the best sum. There are about n²/2 pairs.
The naive version adds up nums[i] through nums[j] for each pair, which costs another loop and makes the total O(n³). The fix is to notice that for a fixed i, moving j one step to the right just adds one element. A running sum that starts at 0 for each new i and grows by nums[j] gives every subarray sum in constant time. That brings the total to O(n²), and the only extra memory is a few integers.
Take i = 2 in the example. As j goes from 2 to 7, the running sums are 6, 4, 1, 2, 7, 1. The best of these is 7, which is also the best over all pairs.
Other lessons on this problem make it faster, down to a single pass. This one is the plain version and is the right place to see why the problem is hard to do cheaply.
Step by step
- Declare
maxSumasnums[0]. Do not start at 0. If every number is negative, the best subarray has a negative sum, and a starting 0 would never be beaten.nums[0]is itself a subarray, so it is a safe starting answer. - Loop over the first index.
for i in 0..<nums.countvisits every possible start. The half-open range0..<nstops beforen. - Declare
var sum = 0inside this loop. It is reset for every new start, which means "an empty subarray so far", and it is not needed outside the loop. - Loop over the last index.
for j in i..<nums.countstarts ati, so the subarray holds at least one element and the last index is never before the first. - Add
nums[j]tosum. Nowsumis exactly the sum ofnums[i...j]. - Update the best.
maxSum = max(maxSum, sum)keeps the larger of the two in one line. - Return
maxSumafter both loops finish.
The solution in Swift
class Solution {
func maxSumSubarray(_ nums: [Int]) -> Int {
var maxSum = nums[0]
for i in 0..<nums.count {
var sum = 0
for j in i..<nums.count {
sum += nums[j]
maxSum = max(maxSum, sum)
}
}
return maxSum
}
}
Swift details worth knowing:
- Ranges.
0..<nexcludesn, and0...nincludes it. Here the half-open form matches array indices, which run from 0 tocount - 1. - Inner ranges can't run backwards.
i..<nums.countis only valid whilei <= nums.count. Swift stops with a run-time error for a range whose lower bound is greater than its upper bound. Becauseiis always less thannums.countinside the outer loop, this never happens. - The global
max.max(a, b)is a free function from Swift's standard library, so it needs no import and no receiver. - Naming a variable
sumis fine. Swift has no global function calledsum. Arrays do havereduce, which could add elements, but the loop keeps the memory constant and shows the idea. - Overflow traps.
Intis 64 bits in Swift, and arithmetic that overflows stops the program instead of wrapping. Ordinary inputs are nowhere near that limit. - Empty arrays.
nums[0]on an empty array crashes the program at run time, so this solution assumes at least one element.
Calling it:
let solution = Solution()
print(solution.maxSumSubarray([-2, -5, 6, -2, -3, 1, 5, -6]))
print(solution.maxSumSubarray([-3, -1, -2]))
print(solution.maxSumSubarray([1, 2, 3]))
7
-1
6
Complexity
Time: O(n²). The outer loop runs n times, and for index i the inner loop runs n - i times. That adds up to n(n + 1)/2 steps, each with constant work.
Space: O(1). The code keeps only maxSum, sum and the two loop indices.
Edge cases and common mistakes
- All negative numbers.
[-3, -1, -2]returns-1, the single best element. StartingmaxSumat 0 would wrongly return 0, which is the sum of no elements. - A single element.
[5]returns 5, since the loops visit exactly one pair. - All positive numbers. The whole array wins:
[1, 2, 3]returns 6. - Declaring
sumoutside the outer loop. The sums of earlier starts would leak into later ones, and the subarrays would no longer be contiguous. - Starting
jat 0. That would add elements beforeiand break the meaning of the pair. - Updating
maxSumoutside the inner loop. Only the sum of the longest subarray from eachiwould be compared, and shorter ones, which may be better, would be skipped.
Testing
[-2, -5, 6, -2, -3, 1, 5, -6]gives7.[-3, -1, -2]gives-1.[5]gives5.[1, 2, 3]gives6.
