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.
The problem
Section titled “The problem”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.
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.
Constraints
Section titled “Constraints”- 1 ≤ n ≤ 100,000
- −10,000 ≤ nums[i] ≤ 10,000
Clarifying questions
Section titled “Clarifying questions”- 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.
Spot the pattern
Section titled “Spot the pattern”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.
Solutions
Section titled “Solutions”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.
Every start O(n²)
The idea
Section titled “The idea”Try every start i. From there, move j to the right and keep a running total, so each longer
subarray costs one more addition. Keep the largest total ever seen.
It checks every subarray, so it cannot miss the best one. But there are about n² / 2 of them.
Walkthrough
Section titled “Walkthrough”- best = nums[0]
- for i in range(len(nums)):
- total = 0
- for j in range(i, len(nums)):
- total += nums[j]
- best = max(best, total)
- return best
Try every start i. From each start, extend j to the right and keep a running total, so each new subarray costs one addition.
class Solution { // Every start: from each start, extend to the right with a running total. // About n * n / 2 subarrays, so O(n^2) time and O(1) memory. public int maxSubArray(int[] nums) { int best = nums[0]; for (int i = 0; i < nums.length; i++) { int total = 0; // Each longer subarray is the last one plus one number: one addition. for (int j = i; j < nums.length; j++) { total += nums[j]; best = Math.max(best, total); } } return best; }}# Every start: from each start, extend to the right with a running total.# About n * n / 2 subarrays, so O(n^2) time and O(1) memory.def max_sub_array(nums: list[int]) -> int: best = nums[0] for i in range(len(nums)): total = 0 # Each longer subarray is the last one plus one number: one addition. for j in range(i, len(nums)): total += nums[j] best = max(best, total) return bestpublic class Solution{ // Every start: from each start, extend to the right with a running total. // About n * n / 2 subarrays, so O(n^2) time and O(1) memory. public int MaxSubArray(int[] nums) { int best = nums[0]; for (int i = 0; i < nums.Length; i++) { int total = 0; // Each longer subarray is the last one plus one number: one addition. for (int j = i; j < nums.Length; j++) { total += nums[j]; best = System.Math.Max(best, total); } } return best; }}// Every start: from each start, extend to the right with a running total.// About n * n / 2 subarrays, so O(n^2) time and O(1) memory.func maxSubArray(nums []int) int { best := nums[0] for i := range nums { total := 0 // Each longer subarray is the last one plus one number: one addition. for j := i; j < len(nums); j++ { total += nums[j] best = max(best, total) } } return best}// Every start: from each start, extend to the right with a running total.// About n * n / 2 subarrays, so O(n^2) time and O(1) memory.function maxSubArray(nums: number[]): number { let best = nums[0]; for (let i = 0; i < nums.length; i++) { let total = 0; // Each longer subarray is the last one plus one number: one addition. for (let j = i; j < nums.length; j++) { total += nums[j]; best = Math.max(best, total); } } return best;}1 Find the work that repeats
The lit lines add one number and compare. They sit inside two loops. That small job does not grow with n, so each run is 1 step.
- best = nums[0]
- for i in range(len(nums)):
- total = 0
- for j in range(i, len(nums)):
- total += nums[j]
- best = max(best, total)
- return best
2 Count the runs on Example 1
Example 1 has 8 numbers. Start 0 has 8 subarrays, start 1 has 7, and so on down to start 7, which has 1.
8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 = 36 runs.
start i 0 1 2 3 4 5 6 7 inner runs 8 7 6 5 4 3 2 1 total8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 = 363 Write the count for any n
n + (n − 1) + … + 1 = n × (n + 1) / 2 = ½n² + ½nCheck it with Example 1: 8 × 9 / 2 = 36.
4 Keep only the part that grows fastest
- Drop the +½n. Next to ½n² it soon stops mattering.
- Drop the ½. It does not change how fast the count grows.
What is left is n².
So the time is O(n²).
S Space: count the extra memory the same way
The code keeps
best,i,jandtotal. That is fixed, for any n, so the space is O(1).bestijtotal
Kadane's algorithm O(n)
The idea
Section titled “The idea”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.
Walkthrough
Section titled “Walkthrough”- best_ending_here = best = nums[0]
- for number in nums[1:]:
- best_ending_here = max(number, best_ending_here + number)
- best = max(best, best_ending_here)
- 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.
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; }}# Kadane's algorithm: best_ending_here is the best sum of a subarray ending here.# Extend it or start fresh, whichever is bigger. O(n) time, O(1) memory.def max_sub_array(nums: list[int]) -> int: # Not 0: the subarray cannot be empty, so all negatives must give a negative. best_ending_here = best = nums[0] for i in range(1, len(nums)): number = nums[i] # A negative run so far only drags number down, so then start again at number. best_ending_here = max(number, best_ending_here + number) best = max(best, best_ending_here) return bestpublic 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 = System.Math.Max(number, bestEndingHere + number); best = System.Math.Max(best, bestEndingHere); } return best; }}// 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.func maxSubArray(nums []int) int { // Not 0: the subarray cannot be empty, so all negatives must give a negative. bestEndingHere, best := nums[0], nums[0] for _, number := range nums[1:] { // A negative run so far only drags number down, so then start again at number. bestEndingHere = max(number, bestEndingHere+number) best = max(best, bestEndingHere) } return best}// 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.function maxSubArray(nums: number[]): number { // Not 0: the subarray cannot be empty, so all negatives must give a negative. let bestEndingHere = nums[0]; let best = nums[0]; for (let i = 1; i < nums.length; i++) { const 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;}1 Find the work that repeats
Each run of the loop does one addition and two
maxcalls. That is 1 step. There is one loop.- best_ending_here = best = nums[0]
- for number in nums[1:]:
- best_ending_here = max(number, best_ending_here + number)
- best = max(best, best_ending_here)
- return best
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 1 run 2 run 3 run 4 run 5 run 6 run 7 number -5 4 -1 2 1 -6 2 best_ending_here -2 4 3 5 6 0 2 3 Write the count for any n
n − 1 runsCheck it with Example 1: 8 − 1 = 7.
4 Keep only the part that grows fastest
Drop the − 1. What is left is n.
So the time is O(n).
S Space: count the extra memory the same way
The code keeps two numbers,
best_ending_hereandbest. The space is O(1).best_ending_herebest
Tested at the limits
Section titled “Tested at the limits”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 inputs | Result |
|---|---|
| 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. |
The designbeyondcode digest, every Saturday
New deep dives, interview tips and site news in one short email. Free. Leave with one click.
We never sell or share your email. We only use it to send this newsletter.