Skip to content

Coding patternsBlind 75

Maximum Subarray

A running sum that has gone negative can only hurt what comes next, so drop it and start fresh. Solved two ways, every start in O(n²) and Kadane's algorithm in O(n), with a step-through, code in five languages and the Big O worked out from the code.

You are given an array nums of whole numbers (call its length n). A subarray is a run of one or more numbers that sit next to each other. Find the subarray with the largest sum, and return that sum.

Example 1

Input: nums = [3, -5, 4, -1, 2, 1, -6, 2]

Output: 6

4 − 1 + 2 + 1 = 6. Adding the 3 and −5 in front would lower it, and so would the −6 after it.

nums
30-5142-132415-6627

Example 2

Input: nums = [-3, -1, -2]

Output: -1

Every number is negative. The best subarray is the single largest number.

Example 3

Input: nums = [5, -2, 7]

Output: 10

The −2 is worth crossing to reach the 7: 5 − 2 + 7 = 10.

  • 1 ≤ n ≤ 100,000
  • −10,000 ≤ nums[i] ≤ 10,000
Can the subarray be empty?
No. It has at least one number, so with all negatives the answer is negative.
Do we return the sum or the subarray?
Only the sum.
Must the numbers be next to each other?
Yes. That is what makes it a subarray and not a subset.
Can the sum overflow?
No. At most 100,000 × 10,000 = 1,000,000,000, which fits in 32 bits.

The best subarray that ends at index i either is nums[i] alone, or extends the best subarray that ends at i − 1. So each answer is built from the one just before it. That is dynamic programming with a single number of state, known as Kadane’s algorithm.

Adding up every subarray from scratch is O(n³), just a slower brute force. A divide and conquer answer runs in O(n log n), still slower than Kadane.

Kadane's algorithm O(n)

Walk the array once. Keep best_ending_here: the best sum of a subarray that ends at the current number.

For each new number there are only two choices. Extend the run that ended just before, giving best_ending_here + number, or start a new run at number. Take the bigger one: best_ending_here = max(number, best_ending_here + number). In plain words: if the run so far has gone negative, it can only drag number down, so drop it.

Every subarray ends somewhere, so the answer is the largest best_ending_here ever seen. Keep it in best.

Why it never misses the answer: the best subarray ends at some index. At that index, best_ending_here is at least its sum, because best_ending_here is the best of all subarrays ending there.

nums
30-5142-132415-6627
i
best_ending_here
3
best
3
  1. best_ending_here = best = nums[0]
  2. for number in nums[1:]:
  3. best_ending_here = max(number, best_ending_here + number)
  4. best = max(best, best_ending_here)
  5. return best

best_ending_here is the best sum of a subarray that ends here. At index 0 that is just 3. best is the largest best_ending_here seen.

1 / 9
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Kadane's algorithm: bestEndingHere is the best sum of a subarray ending here.
// Extend it or start fresh, whichever is bigger. O(n) time, O(1) memory.
public int maxSubArray(int[] nums) {
// Not 0: the subarray cannot be empty, so all negatives must give a negative.
int bestEndingHere = nums[0], best = nums[0];
for (int i = 1; i < nums.length; i++) {
int number = nums[i];
// A negative run so far only drags number down, so then start again at number.
bestEndingHere = Math.max(number, bestEndingHere + number);
best = Math.max(best, bestEndingHere);
}
return best;
}
}
Time O(n)Space O(1)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    Each run of the loop does one addition and two max calls. That is 1 step. There is one loop.

    1. best_ending_here = best = nums[0]
    2. for number in nums[1:]:
    3. best_ending_here = max(number, best_ending_here + number)
    4. best = max(best, best_ending_here)
    5. return best
  2. 2 Count the runs on Example 1

    Example 1 has 8 numbers. The first one sets best_ending_here, and the loop handles the other 7.

    7 runs.

    run 1run 2run 3run 4run 5run 6run 7
    number-54-121-62
    best_ending_here-2435602
  3. 3 Write the count for any n

    n − 1 runs

    Check it with Example 1: 8 − 1 = 7.

  4. 4 Keep only the part that grows fastest

    Drop the − 1. What is left is n.

    So the time is O(n).

  5. S Space: count the extra memory the same way

    The code keeps two numbers, best_ending_here and best. The space is O(1).

    best_ending_herebest

2,000 random arrays of 1 to 10 numbers from −5 to 5, and about one in five all negative. Every start and Kadane's algorithm gave the same answer every time.

Largest inputsResult
100,000 numbers (the most allowed), from −10,000 to 10,000. Kadane algorithm. Also run with every number negative.0.01 s, and under 0.001 MB of extra memory: just two numbers.
2,000 numbers. Every start, about 2 million sums.0.1 s. At 100,000 numbers it would add 5 billion sums, which is why Kadane matters.