Kadane and Maximum Subarray
Kadane’s algorithm is the shortest useful algorithm in the interview canon: three lines, time, space, and it answers “what is the largest sum any contiguous subarray can have?” over arrays that contain negative numbers — where a sliding window cannot go.
The whole thing rests on one decision made once per element:
Does the best subarray ending here extend the previous one, or start fresh at me?
If the running sum you inherit is negative, it can only drag you down, so you restart. That is it. Everything else on this page is that idea wearing a different hat.
What you’ll learn
- The extend-or-restart decision, and why it is provably optimal.
- Kadane framed as a one-line DP — which is how you extend it.
- Why the all-negative case breaks a naive implementation.
- The three big variants: product (signs flip), circular (wrap around), and absolute value.
- Three real LeetCode problems solved in the browser: 53, 152, 918.
The cue
The pattern
def max_subarray(nums):
best = current = nums[0] # NOT 0 -- see the all-negative note
for x in nums[1:]:
current = max(x, current + x) # restart at x, or extend by x
best = max(best, current)
return best
print(max_subarray([-2, 1, -3, 4, -1, 2, 1, -5, 4])) # 6 -> [4, -1, 2, 1]
print(max_subarray([-3, -1, -2])) # -1 -> [-1]def max_subarray(nums):
best = current = nums[0] # NOT 0 -- see the all-negative note
for x in nums[1:]:
current = max(x, current + x) # restart at x, or extend by x
best = max(best, current)
return best
print(max_subarray([-2, 1, -3, 4, -1, 2, 1, -5, 4])) # 6 -> [4, -1, 2, 1]
print(max_subarray([-3, -1, -2])) # -1 -> [-1]currentcurrent means the best sum of any subarray ending exactly at this
index. bestbest is the best of those over all indices. Two variables, one
pass.
Why extend-or-restart is optimal
Let current[i]current[i] be the largest sum of a subarray ending at index ii. Any
such subarray either is just nums[i]nums[i], or is a subarray ending at i-1i-1
extended by nums[i]nums[i]. The best of the second kind is
current[i-1] + nums[i]current[i-1] + nums[i]. So
and the answer is . That is a complete DP, and
because current[i]current[i] depends only on current[i-1]current[i-1], the table collapses to
one variable — which is why the space is .
Reading it as a DP is what lets you extend it: for the product variant you simply need two rolling states instead of one.
| Time | Space | |
|---|---|---|
| Every subarray, summed | ||
| Every subarray, running sum | ||
| Divide and conquer | ||
| Kadane |
The variant map
| Variant | What changes | Canonical problem |
|---|---|---|
| Maximum sum | The base template | 53 Maximum Subarray |
| Maximum product | Track max and min; a negative swaps them | 152 Maximum Product Subarray |
| Circular | Answer is max(best_max, total - best_min)max(best_max, total - best_min), guarded | 918 Maximum Sum Circular Subarray |
| Maximum absolute sum | max(best_max, -best_min)max(best_max, -best_min) | 1749 |
| Return the indices | Record startstart when you restart, and (start, i)(start, i) when bestbest improves | 53 follow-up |
| Minimum sum | Flip every maxmax to minmin | Used as a subroutine by 918 |
Practice — real LeetCode problems
LC 53 — Maximum Subarray · Medium
Problem. Given an integer array numsnums, find the contiguous subarray
with at least one element that has the largest sum, and return that sum.
Constraints. 1 <= len(nums) <= 10^51 <= len(nums) <= 10^5, -10^4 <= nums[i] <= 10^4-10^4 <= nums[i] <= 10^4.
Examples. [-2,1,-3,4,-1,2,1,-5,4][-2,1,-3,4,-1,2,1,-5,4] gives 66 ([4,-1,2,1][4,-1,2,1]) ·
[1][1] gives 11 · [5,4,-1,7,8][5,4,-1,7,8] gives 2323 · [-3,-1,-2][-3,-1,-2] gives -1-1
Editorial — approach, complexity, follow-ups
currentcurrent is the best sum of a subarray ending at the current index. If the
inherited currentcurrent is negative, xx alone beats current + xcurrent + x, so the
subarray restarts — which is what max(x, current + x)max(x, current + x) expresses without
an explicit branch.
Time . Space .
The [-3,-1,-2][-3,-1,-2] case is the whole reason to be careful with
initialisation. Every currentcurrent is negative, every restart is negative, and
the correct answer is the least bad single element.
Follow-ups you should expect:
- “Return the subarray, not the sum.” Keep a
startstartthat resets toiiwhenever you restart, and record(start, i)(start, i)wheneverbestbestimproves. - “Do it with divide and conquer.” LeetCode explicitly asks for this. Split in half; the answer is the best of (left half, right half, best crossing subarray), where the crossing one is found by expanding outward from the midpoint. — strictly worse, but it is a legitimate demonstration of the technique and a common ask.
- “What if the array is a stream?” Kadane is already online: it needs only the current element and two numbers of state.
- “At most
kkelements?” No longer Kadane — that is a windowed DP needing a monotonic deque.
LC 152 — Maximum Product Subarray · Medium
Problem. Given an integer array numsnums, find the contiguous subarray
with the largest product, and return that product.
Constraints. 1 <= len(nums) <= 2 * 10^41 <= len(nums) <= 2 * 10^4, -10 <= nums[i] <= 10-10 <= nums[i] <= 10.
The answer fits in a 32-bit integer.
Examples. [2,3,-2,4][2,3,-2,4] gives 66 ([2,3][2,3]) · [-2,0,-1][-2,0,-1] gives 00 ·
[-2,3,-4][-2,3,-4] gives 2424 (the whole array) · [0,2][0,2] gives 22
Editorial — approach, complexity, follow-ups
Sums are monotonic under extension; products are not. Multiplying by a
negative number inverts the ordering, so the smallest product so far is
a genuine candidate for the largest product next. Carrying only hihi
throws away exactly the information you need on [-2,3,-4][-2,3,-4], where the
answer uses both negatives.
So maintain:
hihi= largest product of a subarray ending here,lolo= smallest product of a subarray ending here.
When x < 0x < 0, swap them first. Then update both against xx alone (which
is the “restart here” option) and against the extended product.
Time . Space .
Follow-ups you should expect: “Why not divide by the outgoing element
to slide a window?” — because a zero makes division undefined and destroys
the invariant. “Maximum absolute sum (1749)?” — run Kadane for max and
min, and return max(best_max, -best_min)max(best_max, -best_min). “Count of negatives approach?”
— there is a neat prefix/suffix-scan solution too, worth mentioning: scan
left-to-right and right-to-left, resetting on zeros, and take the best
product seen.
LC 918 — Maximum Sum Circular Subarray · Medium
Problem. Given a circular integer array numsnums, return the maximum
sum of a non-empty subarray. The subarray may wrap from the end back to
the beginning, but may not include any element more than once.
Constraints. 1 <= len(nums) <= 3 * 10^41 <= len(nums) <= 3 * 10^4,
-3 * 10^4 <= nums[i] <= 3 * 10^4-3 * 10^4 <= nums[i] <= 3 * 10^4.
Examples. [1,-2,3,-2][1,-2,3,-2] gives 33 · [5,-3,5][5,-3,5] gives 1010 (wraps) ·
[-3,-2,-3][-3,-2,-3] gives -2-2 · [-2,4,-5,4,-5,9,4][-2,4,-5,4,-5,9,4] gives 1515
Editorial — approach, complexity, follow-ups
Split into two cases:
- The answer does not wrap. Then it is ordinary Kadane:
best_maxbest_max. - The answer wraps. Then the elements it excludes form a single
contiguous, non-wrapping block in the middle. Maximising the wrapped
sum is therefore the same as minimising the excluded block:
total - best_mintotal - best_min.
Run Kadane twice in the same pass — once with maxmax, once with minmin —
and return the better of the two candidates.
Time , single pass. Space .
Follow-ups you should expect: “Why can’t you just concatenate the array
with itself and run Kadane?” — because that permits subarrays longer than
nn, reusing elements; you would need a length cap, which turns it into a
windowed DP with a monotonic deque. “Return the
indices?” — track them in both Kadanes and report from whichever branch
won. “Circular minimum subarray?” — mirror the whole argument.
LeetCode problem set
| # | Problem | Difficulty | The twist |
|---|---|---|---|
| 121 | Best Time to Buy and Sell Stock | Easy | Kadane over day-to-day differences — or just track the running minimum |
| 53 | Maximum Subarray | Medium | The base template; the all-negative case is the trap |
| 152 | Maximum Product Subarray | Medium | Two rolling states; a negative swaps max and min |
| 918 | Maximum Sum Circular Subarray | Medium | total - min_subarraytotal - min_subarray for the wrapping case, plus the all-negative guard |
| 1749 | Maximum Absolute Sum of Any Subarray | Medium | Run both Kadanes, return max(best_max, -best_min)max(best_max, -best_min) |
| 2606 | Find the Substring With Maximum Cost | Medium | Map characters to values, then plain Kadane — here an empty answer is allowed, so 00 is a valid floor |
Interview follow-ups
| They ask | What they’re checking | The answer |
|---|---|---|
| “Why does extend-or-restart work?” | Whether you can justify it | It is the DP current[i] = max(nums[i], current[i-1] + nums[i])current[i] = max(nums[i], current[i-1] + nums[i]); a negative inherited sum can only reduce any extension, so discarding it is never worse |
| “Can the subarray be empty?” | Whether you clarify the spec | Ask. If yes, floor at 00; if no, start from nums[0]nums[0]. It changes the answer on all-negative input |
| “Return the subarray itself” | Bookkeeping | Reset start = istart = i on a restart; save (start, i)(start, i) when bestbest improves |
| “Divide and conquer version” | Breadth (LC 53 asks explicitly) | Best of left, right, and the best crossing subarray from the midpoint — |
| “Why two states for the product?” | Depth | Multiplying by a negative inverts the ordering, so the running minimum is a candidate for the next maximum |
| “Handle a stream” | Practicality | Already online — state, one element at a time |
“At most kk elements” | Knowing the boundary | No longer Kadane; it becomes a windowed DP + monotonic deque |
Edge-case checklist
- All negative — the headline trap. Answer is the largest single
element, not
00. - Single element — must return that element, even if negative.
- All positive — the answer is the entire array; a sanity check.
- Contains zeros — harmless for sums; the reset case for products.
- Empty subarray allowed? — a real specification question (2606 vs 53); ask rather than assume.
- Wrapping answer covering the whole array (918) — possible when all values are positive; make sure your formula does not exclude everything.
- Two negatives making a positive product (152) —
[-2,3,-4][-2,3,-4]gives2424; the case that catches single-state solutions.
Recap
- Kadane is one decision per element: extend the previous subarray, or
restart here —
current = max(x, current + x)current = max(x, current + x). - It is a collapsed DP, which is why it is time and space, and why the variants are just “carry more state”.
- Never initialise to
00unless the empty subarray is explicitly allowed — otherwise all-negative input returns the wrong answer. - Product needs two states because negatives invert the ordering.
- Circular reframes a wrapping subarray as the contiguous block it
excludes:
max(best_max, total - best_min)max(best_max, total - best_min), guarded against all-negative input. - Kadane finds the best subarray; counting subarrays is prefix sums + a hash map instead.
Next: Cyclic Sort — the trick for arrays that are a permutation of
1..n1..n, where the values themselves tell you where they belong.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
