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
Section titled “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
Section titled “The cue”The pattern
Section titled “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]current means the best sum of any subarray ending exactly at this
index. best is the best of those over all indices. Two variables, one
pass.
Why extend-or-restart is optimal
Section titled “Why extend-or-restart is optimal”Let current[i] be the largest sum of a subarray ending at index i. Any
such subarray either is just nums[i], or is a subarray ending at i-1
extended by nums[i]. The best of the second kind is
current[i-1] + nums[i]. So
and the answer is . That is a complete DP, and
because current[i] depends only on 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 |
Visual intuition
Section titled “Visual intuition”One question per step: is it better to extend the run I am on, or throw it away and start fresh here? Watch the frames where the run restarts.
The restart happens exactly when the running total has gone negative: a negative prefix can only drag down whatever follows, so dropping it is always at least as good. That single observation is the whole algorithm.
Complexity
Section titled “Complexity”| Approach | Time | Space | Note |
|---|---|---|---|
| Every subarray, summed from scratch | the naive triple loop | ||
| Every subarray, running sum | fine up to a few thousand | ||
| Divide and conquer | worth mentioning; rarely the intended answer | ||
| Kadane | one pass, two variables |
Kadane’s is optimal: any correct algorithm must read every element at least once, so is a lower bound.
The variant map
Section titled “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), guarded | 918 Maximum Sum Circular Subarray |
| Maximum absolute sum | max(best_max, -best_min) | 1749 |
| Return the indices | Record start when you restart, and (start, i) when best improves | 53 follow-up |
| Minimum sum | Flip every max to min | Used as a subroutine by 918 |
Practice — real LeetCode problems
Section titled “Practice — real LeetCode problems”LC 53 — Maximum Subarray · Medium
Section titled “LC 53 — Maximum Subarray · Medium”Problem. Given an integer array nums, find the contiguous subarray
with at least one element that has the largest sum, and return that sum.
Constraints. 1 <= len(nums) <= 10^5, -10^4 <= nums[i] <= 10^4.
Examples. [-2,1,-3,4,-1,2,1,-5,4] gives 6 ([4,-1,2,1]) ·
[1] gives 1 · [5,4,-1,7,8] gives 23 · [-3,-1,-2] gives -1
Editorial — approach, complexity, follow-ups
current is the best sum of a subarray ending at the current index. If the
inherited current is negative, x alone beats current + x, so the
subarray restarts — which is what max(x, current + x) expresses without
an explicit branch.
Time . Space .
The [-3,-1,-2] case is the whole reason to be careful with
initialisation. Every current 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
startthat resets toiwhenever you restart, and record(start, i)wheneverbestimproves. - “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
kelements?” No longer Kadane — that is a windowed DP needing a monotonic deque.
LC 152 — Maximum Product Subarray · Medium
Section titled “LC 152 — Maximum Product Subarray · Medium”Problem. Given an integer array nums, find the contiguous subarray
with the largest product, and return that product.
Constraints. 1 <= len(nums) <= 2 * 10^4, -10 <= nums[i] <= 10.
The answer fits in a 32-bit integer.
Examples. [2,3,-2,4] gives 6 ([2,3]) · [-2,0,-1] gives 0 ·
[-2,3,-4] gives 24 (the whole array) · [0,2] gives 2
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 hi
throws away exactly the information you need on [-2,3,-4], where the
answer uses both negatives.
So maintain:
hi= largest product of a subarray ending here,lo= smallest product of a subarray ending here.
When x < 0, swap them first. Then update both against x 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). “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
Section titled “LC 918 — Maximum Sum Circular Subarray · Medium”Problem. Given a circular integer array nums, 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^4,
-3 * 10^4 <= nums[i] <= 3 * 10^4.
Examples. [1,-2,3,-2] gives 3 · [5,-3,5] gives 10 (wraps) ·
[-3,-2,-3] gives -2 · [-2,4,-5,4,-5,9,4] gives 15
Editorial — approach, complexity, follow-ups
Split into two cases:
- The answer does not wrap. Then it is ordinary Kadane:
best_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_min.
Run Kadane twice in the same pass — once with max, once with min —
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
n, 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
Section titled “LeetCode problem set”Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.
Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.
- 121Best Time to Buy and Sell StockeasyKadane over day-to-day differences -- or just track the running minimum
- 53Maximum SubarraymediumThe base template; the all-negative case is the trap
- 152Maximum Product SubarraymediumTwo rolling states; a negative swaps max and min
- 918Maximum Sum Circular Subarraymedium`total - min_subarray` for the wrapping case, plus the all-negative guard
- 1749Maximum Absolute Sum of Any SubarraymediumRun both Kadanes, return `max(best_max, -best_min)`
- 2606Find the Substring With Maximum CostmediumMap characters to values, then plain Kadane -- here an empty answer *is* allowed, so `0` is a valid floor
Dry run
Section titled “Dry run”nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]. The two decision columns are what to
show an interviewer.
i | nums[i] | extend (cur + v) | restart (v) | cur | best | decision |
|---|---|---|---|---|---|---|
| 0 | −2 | — | — | −2 | −2 | seed |
| 1 | 1 | −1 | 1 | 1 | 1 | restart — the −2 prefix only hurts |
| 2 | −3 | −2 | −3 | −2 | 1 | extend (both bad; extending is less bad) |
| 3 | 4 | 2 | 4 | 4 | 4 | restart |
| 4 | −1 | 3 | −1 | 3 | 4 | extend |
| 5 | 2 | 5 | 2 | 5 | 5 | extend |
| 6 | 1 | 6 | 1 | 6 | 6 | extend — new best |
| 7 | −5 | 1 | −5 | 1 | 6 | extend (still positive, so worth keeping) |
| 8 | 4 | 5 | 4 | 5 | 6 | extend |
Answer 6, from [4, -1, 2, 1] at indices 3..6.
Two observations worth stating out loud:
- The restart at
i = 1andi = 3happens exactly whencurwas negative. That is not a coincidence —cur + v < vis algebraically the same ascur < 0. Some write it as “if cur < 0: cur = 0”, and the two are identical. bestwas last updated ati = 6, three steps before the end. The maximum subarray does not have to end at the last element, which is whybestis a separate variable fromcur. Returningcuris the classic wrong answer.
Interview follow-ups
Section titled “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]); 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 0; if no, start from nums[0]. It changes the answer on all-negative input |
| “Return the subarray itself” | Bookkeeping | Reset start = i on a restart; save (start, i) when best 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 k elements” | Knowing the boundary | No longer Kadane; it becomes a windowed DP + monotonic deque |
Edge-case checklist
Section titled “Edge-case checklist”- All negative — the headline trap. Answer is the largest single
element, not
0. - 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]gives24; the case that catches single-state solutions.
Self-check
Section titled “Self-check”-
Why are `cur` and `best` separate variables?
cur is the best subarray ending exactly at i; best is the maximum over all i. Returning cur is the standard wrong answer and it passes any input whose answer happens to end at the last element.
pch.quizShowAnswer
B — Because the maximum subarray need not end at the last element, so the running value and the best-ever value differ — cur is the best subarray ending exactly at i; best is the maximum over all i. Returning cur is the standard wrong answer and it passes any input whose answer happens to end at the last element.
-
The array is all negative: [-3, -1, -4]. What does a correct Kadane return?
This is the edge case that catches implementations initialised with best = 0. LC 53 requires a non-empty subarray, so seed both cur and best with nums[0], never with 0. If a problem does allow the empty subarray, 0 is right — clarify which.
pch.quizShowAnswer
B — -1, the largest single element, because the subarray must be non-empty — This is the edge case that catches implementations initialised with best = 0. LC 53 requires a non-empty subarray, so seed both cur and best with nums[0], never with 0. If a problem does allow the empty subarray, 0 is right — clarify which.
-
`cur = max(v, cur + v)` restarts the run. When exactly does that happen?
The two formulations — max(v, cur + v) and 'if cur < 0: cur = 0' — are algebraically identical. A negative prefix can only reduce whatever follows it, so discarding it is always at least as good.
pch.quizShowAnswer
B — Exactly when cur is negative, since cur + v < v is equivalent to cur < 0 — The two formulations — max(v, cur + v) and 'if cur < 0: cur = 0' — are algebraically identical. A negative prefix can only reduce whatever follows it, so discarding it is always at least as good.
-
Now return the subarray itself, not just its sum. What changes?
Three extra variables and no change in complexity. This is the most common follow-up on LC 53, and getting the bookkeeping right — reset start on restart, capture both bounds on improvement — is the whole of it.
pch.quizShowAnswer
B — Track a start index that resets on every restart, and record start and end whenever best improves — Three extra variables and no change in complexity. This is the most common follow-up on LC 53, and getting the bookkeeping right — reset start on restart, capture both bounds on improvement — is the whole of it.
-
The array is circular (LC 918). What is the trick?
A wrapping subarray is exactly the complement of a non-wrapping one, so minimising the middle maximises the wrap. The guard matters: if every element is negative the 'total minus min' branch returns the empty subarray, so fall back to the plain answer.
pch.quizShowAnswer
B — The answer is either a normal maximum subarray, or the total minus the minimum subarray — take the larger, guarding the all-negative case — A wrapping subarray is exactly the complement of a non-wrapping one, so minimising the middle maximises the wrap. The guard matters: if every element is negative the 'total minus min' branch returns the empty subarray, so fall back to the plain answer.
Recall card
Section titled “Recall card”- Cue — “maximum sum / product of a contiguous subarray”, and negative values are allowed (which is what rules out a sliding window).
- Invariant —
curis the best subarray ending exactly ati;bestis the maximum over alli. - Template —
cur = max(v, cur + v), thenbest = max(best, cur). Seed both withnums[0], never with 0. - Complexity — time, space, and optimal.
- Remember — the restart condition is exactly “
curwent negative”. Returnbest, notcur. - Variants — circular (total − minimum subarray, with an all-negative guard);
product (track min and max, because a negative flips them); return the
subarray (track
start).
- Kadane is one decision per element: extend the previous subarray, or
restart here —
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
0unless 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), 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..n, where the values themselves tell you where they belong.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading