LeetCode #53 Medium

Maximum Subarray

Maximum Subarray is LeetCode 53 (Medium). Given an integer array nums, find the contiguous subarray with maximum sum (it must contain at least one element), and return that sum.

  • A subarray is a run of neighbouring elements; you cannot skip elements.
  • The array may contain negative numbers, and may be all negative. The subarray must still contain at least one element.
  • Follow-up: after the O(n) solution, try divide and conquer.

With up to 10⁵ elements, checking every subarray (about 5 · 10⁹ pairs) is too slow; the target is one pass.

Constraints
  • 1 <= nums.length <= 10⁵
  • -10⁴ <= nums[i] <= 10⁴
arraydpkadane
Open on LeetCode ↗
02

Intuition

The maximum subarray problem asks for the best contiguous run. Kadane's algorithm solves it in one pass by asking a smaller question at each index i:

> What is the best sum of a subarray that ends exactly at i?

That subarray is either nums[i] on its own, or the best one ending at i - 1 with nums[i] added. The answer is the largest of these over all indices. It is dynamic programming with one number of state, so it runs in O(n) time and O(1) space.

How to spot this pattern

"Largest (or smallest) sum of a contiguous subarray" is Kadane's algorithm. The same restart-or-extend idea gives Maximum Product Subarray (track both a max and a min), the circular version (LeetCode 918: total minus the minimum subarray), and best time to buy and sell stock (Kadane on the day-to-day price differences).

03

Approach

Try it first

Before reading on: for [-2, 1, -3, 4, -1, 2, 1, -5, 4], write the best sum of a subarray that ends at each index. When does it pay to throw away everything before the current element? What should the answer be for [-3, -1, -2]?

1

Start with the first element

cur = best = nums[0]: the only subarray ending at index 0 is that element alone. Starting best at 0 would be wrong when every number is negative.

2

Restart or extend at each element

For each next x:

  • cur < 0: start a new run, cur = x;
  • otherwise: extend the run, cur += x.

A negative running sum can only drag the next element down, so dropping it never loses the answer.

3

Track the best run end

best = max(best, cur) after every element, since the optimal subarray can end at any index.

4

Return best

After one pass, best is the maximum subarray sum. Each index was considered as an end exactly once with its best start.

04

Maximum Subarray solution in Python | C++ | Java

▶1class Solution:
▶2 def maxSubArray(self, nums: List[int]) -> int:
▶3 cur = best = nums[0]
▶4 for x in nums[1:]:
▶5 if cur < 0:
▶6 cur = x
▶7 else:
▶8 cur += x
▶9 best = max(best, cur)
▶10 return best
current runnumscur-20-211-3243-142516-5748best runnums[0]-2→cur-2best-2first run: -2 alone
cur-2best run ending at 0
best-2best anywhere
Start. The only subarray ending at index 0 is [-2], so cur = best = -2. Starting from 0 instead would invent an empty subarray, which is wrong when every number is negative.
current runnumscur-20-2111-3243-142516-5748best rundropped-2x1=cur1new best1run -2 < 0 → restart at 1 · new best 1
x1nums[1]
cur1restarted
best1nums[1..1]
The run ending at 0 sums to -2, a negative number, so attaching it would only make any subarray ending here smaller. Throw it away and start a new run at 1. The new run sum 1 is the best so far.
current runnumscur-20-2111-32-243-142516-5748best runrun1+x-3=cur-2best1run 1 ≥ 0 → extend
x-3nums[2]
cur-2extended
best1nums[1..1]
The run ending at 1 sums to 1, which is not negative, so carrying it along can only help. Extend it with -3, even though -3 is negative: the run may still pay off later.
current runnumscur-20-2111-32-2434-142516-5748best rundropped-2x4=cur4new best4run -2 < 0 → restart at 4 · new best 4
x4nums[3]
cur4restarted
best4nums[3..3]
The run ending at 2 sums to -2, a negative number, so attaching it would only make any subarray ending here smaller. Throw it away and start a new run at 4. The new run sum 4 is the best so far.
current runnumscur-20-2111-32-2434-1432516-5748best runrun4+x-1=cur3best4run 4 ≥ 0 → extend
x-1nums[4]
cur3extended
best4nums[3..3]
The run ending at 3 sums to 4, which is not negative, so carrying it along can only help. Extend it with -1, even though -1 is negative: the run may still pay off later.
current runnumscur-20-2111-32-2434-14325516-5748best runrun3+x2=cur5new best5run 3 ≥ 0 → extend · new best 5
x2nums[5]
cur5extended
best5nums[3..5]
The run ending at 4 sums to 3, which is not negative, so carrying it along can only help. Extend it with 2. The new run sum 5 is the best so far.
current runnumscur-20-2111-32-2434-143255166-5748best runrun5+x1=cur6new best6run 5 ≥ 0 → extend · new best 6
x1nums[6]
cur6extended
best6nums[3..6]
The run ending at 5 sums to 5, which is not negative, so carrying it along can only help. Extend it with 1. The new run sum 6 is the best so far.
current runnumscur-20-2111-32-2434-143255166-57148best runrun6+x-5=cur1best6run 6 ≥ 0 → extend
x-5nums[7]
cur1extended
best6nums[3..6]
The run ending at 6 sums to 6, which is not negative, so carrying it along can only help. Extend it with -5, even though -5 is negative: the run may still pay off later.
current runnumscur-20-2111-32-2434-143255166-571485best runrun1+x4=cur5best6run 1 ≥ 0 → extend
x4nums[8]
cur5extended
best6nums[3..6]
The run ending at 7 sums to 1, which is not negative, so carrying it along can only help. Extend it with 4.
numscur-20-2111-32-2434-143255166-571485best runanswer6return 6
best6maximum subarray sum
Done in one pass. The largest value cur reached is 6, the sum of [4, -1, 2, 1]. Every subarray ends somewhere, and at each end cur held the best sum for that end, so nothing was missed.
05

Divide and conquer

best(lo, hi) returns the maximum subarray sum inside nums[lo..hi]. It recurses on both halves, and finds the best run crossing the middle by growing one run leftwards from mid and one rightwards from mid + 1.

▶1class Solution:
▶2 def maxSubArray(self, nums: List[int]) -> int:
▶3 def best(lo: int, hi: int) -> int:
▶4 if lo == hi:
▶5 return nums[lo]
▶6 mid = (lo + hi) // 2
▶7 left_run, total = nums[mid], 0
▶8 for i in range(mid, lo - 1, -1):
▶9 total += nums[i]
▶10 left_run = max(left_run, total)
▶11 right_run, total = nums[mid + 1], 0
▶12 for i in range(mid + 1, hi + 1):
▶13 total += nums[i]
▶14 right_run = max(right_run, total)
▶15 return max(best(lo, mid), best(mid + 1, hi), left_run + right_run)
▶16 
▶17 return best(0, len(nums) - 1)
06

Common pitfalls

Starting best at 0

✗ Wrong
best = 0
cur = 0
✓ Right
cur = best = nums[0]

For [-3, -1, -2] nothing ever beats 0, so the answer comes out as 0, the sum of an empty subarray, which is not allowed. The correct answer is -1.

Restarting whenever the next element is negative

✗ Wrong
if x < 0:
    cur = 0
✓ Right
if cur < 0:
    cur = x
else:
    cur += x

What matters is whether the run so far is negative, not the new element. In [5, 4, -1, 7, 8] restarting at -1 loses the 9 before it and returns 15 instead of 23.

Updating best only when restarting

✗ Wrong
if cur < 0:
    best = max(best, cur)
    cur = x
✓ Right
best = max(best, cur)  # after every element

The best run may never be followed by a restart, as in [1, 2, 3], where the answer 6 is only reached at the last element.

07

Complexity

Time
O(n)
Space
O(1)
One pass with two variables. Checking every subarray is O(n²) with running sums, O(n³) without.
08

Maximum Subarray FAQ

How does Kadane's algorithm work?
  • State: cur = best sum of a subarray ending at the current index; best = best sum seen anywhere.
  • Start: cur = best = nums[0].
  • Each next x: if cur < 0, restart with cur = x; otherwise cur += x. Then best = max(best, cur).
  • Answer: best.
  • Why: a negative run can only lower whatever comes after it, so it is dropped.
  • Complexity: O(n) time, O(1) space.
How do you return the maximum subarray itself, not just its sum?

Track where the current run started: set start = i on every restart. Whenever best improves, save (start, i). At the end, the saved pair gives the subarray.

Is maximum subarray dynamic programming?

Yes. dp[i] = max(nums[i], dp[i-1] + nums[i]) is the best sum ending at index i, and the answer is max(dp). Kadane's algorithm is this recurrence with the table reduced to one variable, since dp[i] only needs dp[i-1].