Skip to content

Beginner: foundations · Arrays and strings · topic 9 of 61 · 35 min

Maximum subarray: Kadane's algorithm

Explanation

The maximum subarray problem asks for the largest sum of a run of neighbouring numbers. Trying every run is O(n²). Kadane's algorithm walks once and keeps the best sum of a run that ENDS at the current position: either extend the previous run or start fresh. It is O(n) time, O(1) space, and its cousin solves the "buy and sell a stock once" problem.

The problem

A subarray is a run of one or more neighbouring items. Given numbers that can be positive or negative, find the largest possible sum of a subarray. For [−2, 1, −3, 4, −1, 2, 1, −5, 4] the answer is 6, from the run [4, −1, 2, 1]. This shows up whenever you want the best stretch of time: the best run of days for a shop, or the strongest period of a signal.

Brute force: try every run

Pick every start l and every end r and add up nums[l..r]. Done naively that is O(n³); keeping a running sum while r moves makes it O(n²). Correct, but too slow for n = 10⁵.

python
def max_sub_brute(nums):
    best = nums[0]
    for l in range(len(nums)):
        running = 0
        for r in range(l, len(nums)):
            running += nums[r]
            best = max(best, running)
    return best

print(max_sub_brute([-2, 1, -3, 4, -1, 2, 1, -5, 4]))   # Output: 6
O(n²) time, O(1) space.

The key idea: best sum ending here

Every subarray ends somewhere. So ask a smaller question for each index i: what is the best sum of a run that ends exactly at i? Call it cur. Such a run either is just nums[i] on its own, or it is the best run ending at i − 1 with nums[i] added. So cur = max(nums[i], cur + nums[i]). In words: if the run so far has a negative sum, drop it and start fresh, because it can only drag the total down.

The answer is the largest cur seen at any index. This is Kadane's algorithm: O(n) time and O(1) space. It is also your first taste of dynamic programming (Module 11): solve a small question for each position and build on the previous answer.

x−21−34−121−54
cur (best ending here)−21−2435615
best so far−211445666
python
def max_subarray_sum(nums):
    best = cur = nums[0]
    for x in nums[1:]:
        cur = max(x, cur + x)
        best = max(best, cur)
    return best

print(max_subarray_sum([-2, 1, -3, 4, -1, 2, 1, -5, 4]))   # Output: 6
print(max_subarray_sum([-3, -1, -2]))                      # Output: -1
Starting best and cur at nums[0] makes all-negative lists work. (nums[1:] copies once; a range loop avoids even that.)

All-negative arrays

If every number is negative, the best non-empty subarray is the single largest number, for example −1 in [−3, −1, −2]. A common buggy version starts best at 0 and returns 0, which is the sum of an EMPTY subarray. Ask the interviewer whether an empty subarray is allowed; usually it is not.

Returning the start and end

To report where the best run is, remember the index where the current run started. When you start fresh at i, set start = i. When cur beats best, save best_start = start and best_end = i. Decide a tie rule and state it. With "start fresh only when the previous run is negative" and "update best only when strictly greater", you get the run that ends earliest, and among those the longest one.

Cousin: buy and sell a stock once

Given daily prices, buy on one day and sell on a later day for the largest profit (or 0 if no profit is possible). Walk once, keep the lowest price so far, and at each day check price − lowest. O(n) time, O(1) space. It is the same idea: the best profit selling today depends only on the cheapest day before today. In fact it equals the maximum subarray of the day-to-day price changes.

python
def max_profit(prices):
    lowest = float("inf")
    best = 0
    for p in prices:
        lowest = min(lowest, p)
        best = max(best, p - lowest)
    return best

print(max_profit([7, 1, 5, 3, 6, 4]))   # Output: 5  (buy at 1, sell at 6)
print(max_profit([7, 6, 4, 3, 1]))      # Output: 0  (prices only fall)

Common mistakes

  • Starting best at 0: wrong for all-negative input. Fix: start with nums[0].
  • Resetting cur to 0 instead of to the current number. Fix: cur = max(x, cur + x).
  • Forgetting the empty list. Fix: decide what to return (0, None or an error) and handle it first.
  • In the stock problem, selling before buying, for example taking max(prices) − min(prices). Fix: only compare with the lowest price BEFORE the current day.
  • Not stating a tie rule when returning indexes. Fix: say it, then make the comparisons (> or >=) match it.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Maximum subarray: Kadane's algorithm · 1/8

Maximum subarray

This lesson solves a classic: the largest sum of a run of neighbouring numbers, using Kadane's algorithm.

Prefer a video by a teacher? Find free videos on “kadane algorithm maximum subarray explained best time to buy and sell stock” on YouTube ↗

In real life: Finding a shop's best stretch of days

A kirana store owner records daily profit or loss after expenses. Some days are negative because of rent or spoilt stock. To plan the next festival season, she wants the stretch of consecutive days with the highest total profit. That is exactly the maximum subarray problem, and Kadane's algorithm answers it for a whole year of data in one pass.

python
daily = [300, -500, 200, 400, -100, 600, -800, 250]
best = cur = daily[0]
start = best_start = best_end = 0
for i in range(1, len(daily)):
    if cur < 0:
        cur, start = daily[i], i
    else:
        cur += daily[i]
    if cur > best:
        best, best_start, best_end = cur, start, i
print(best, best_start, best_end)
Shows 1100 2 5: days 2 to 5 (200 + 400 − 100 + 600) were the best stretch.

Practice (0/3 done)

Task 1 · Code · Python

Write max_subarray_sum(nums) that returns the largest sum of a non-empty run of neighbouring numbers, using Kadane's algorithm (O(n) time, O(1) extra space). If nums is empty, return 0. Example: max_subarray_sum([-2, 1, -3, 4, -1, 2, 1, -5, 4]) returns 6.

Task 2 · Code · Python

prices[i] is a share's price on day i. Write max_profit(prices) that returns the largest profit from buying on one day and selling on a LATER day. If no profit is possible (or there are fewer than 2 days), return 0. O(n) time, O(1) extra space. Example: max_profit([7, 1, 5, 3, 6, 4]) returns 5.

Task 3 · Code · Python

Write max_subarray_indices(nums) for a NON-EMPTY list. Return a tuple (best_sum, start, end) where nums[start..end] (both included) is a run with the largest sum. Tie rule: if several runs have the largest sum, choose the one with the smallest end; if still tied, the one with the smallest start (the longest). O(n) time. Example: max_subarray_indices([-2, 1, -3, 4, -1, 2, 1, -5, 4]) returns (6, 3, 6).

Quick quiz

Question 1

1.In Kadane's algorithm, what does cur hold at index i?

Question 2

2.A version starts best = 0. What does it return for [−4, −2, −7]?

Question 3

3.What are the time and space of Kadane's algorithm?

Question 4

4.Why is max(prices) − min(prices) wrong for the one-trade stock problem?

Question 5

5.When should the current run start fresh at x?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

In Kadane's algorithm, what does cur hold at index i?

Answer: The best sum of a run that ends exactly at i

cur answers the smaller question "best run ending here"; best tracks the overall answer.

A version starts best = 0. What does it return for [−4, −2, −7]?

Answer: 0

No run beats 0, so it wrongly returns 0, the sum of an empty run. Start best at nums[0].

What are the time and space of Kadane's algorithm?

Answer: O(n) time, O(1) space

One pass with two variables.

Why is max(prices) − min(prices) wrong for the one-trade stock problem?

Answer: The maximum may come BEFORE the minimum, which would mean selling before buying

For [9, 1, 2] it gives 8, but the real best is 1 (buy at 1, sell at 2).

When should the current run start fresh at x?

Answer: When the running sum before x is negative

A negative running sum can only lower anything added after it, so drop it.

Why does the subarray-sum-equals-k pattern start with seen = {0: 1}?Related

Answer: So subarrays that start at index 0 are counted

A subarray from index 0 to r sums to k when the running total equals k, i.e. running − k = 0, the empty prefix.

From Prefix sums and difference arrays

For nums = [2, -2, 2] and k = 0, how many subarrays sum to k?Related

Answer: 2

[2, −2] and [−2, 2] both sum to 0; [2, −2, 2] sums to 2.

From Counting and grouping with hashing

Why does maximum product subarray track the minimum product too?Related

Answer: Because multiplying the most negative product by a negative number can give the largest product

For [−2, 3, −4], the run −2 × 3 = −6 is the minimum, and −6 × −4 = 24 is the answer.

From More DP patterns and how to spot them

Why must the numbers be positive for the shortest-subarray-with-sum-at-least-target window?Related

Answer: So removing an item from the left always lowers the sum, which makes "shrink while valid" correct

With negatives, removing an item can raise the sum, so the window can stop shrinking too early or miss the answer.

From Sliding window: variable size

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Arrays and strings” before its exam: 2-D arrays (matrices).
  2. Module 2 exam: Arrays and strings · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Hashing, two pointers and sliding window. 61 topics left in the course before the final exam.

← Previous: Strings: building, comparing and counting