Kadane (best ending here)
best ending at i = max(nums[i], best ending at i−1 + nums[i]).
cur is the maximum subarray sum that ends exactly at i.
01The intuition
Start with what you would do by hand
Daily profits of a small shop over nine days: −2, 1, −3, 4, −1, 2, 1, −5, 4. Which stretch of consecutive days earned the most in total? By hand you add up stretches: days 4 to 7 give 4 − 1 + 2 + 1 = 6; days 4 to 9 give 6 − 5 + 4 = 5; day 9 alone gives 4. The best is 6.
How many stretches are there? Any start day and any end day on or after it: for 9 days that is 45 stretches, and for n days about n2/2. Adding each one up costs up to n more, so about n3/6 additions, or 166 billion for a million days. Even with running sums it is 500 billion stretches to compare.
Build the structure, one piece at a time
Piece one: a different question. Instead of "best stretch anywhere", ask "best stretch that ends exactly on day i". Call it cur[i].
Piece two: the observation that makes the question easy. A stretch ending on day i either is day i alone, or is a stretch ending on day i − 1 with day i added. The best of the second kind is cur[i − 1] + nums[i]. So
cur[i] = max( nums[i], cur[i − 1] + nums[i] )
In words: extend the run if the run so far is positive, otherwise start fresh at day i, because a run that has gone negative only drags you down.
Piece three: a single number best that remembers the largest cur seen.
| day | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| nums | −2 | 1 | −3 | 4 | −1 | 2 | 1 | −5 | 4 |
| cur | −2 | 1 | −2 | 4 | 3 | 5 | 6 | 1 | 5 |
| best | −2 | 1 | 1 | 4 | 4 | 5 | 6 | 6 | 6 |
On day 2, cur[1] = −2 is negative, so cur[2] restarts at 1. On day 4 the same: −2 + 4 = 2 is worse than 4 alone.
Why it is correct
The invariant: cur is the maximum total of any stretch ending exactly at the current day. The two-case argument above shows the formula computes it from the previous day's value, and day 1 is trivially right. Every stretch ends on some day, so the best overall is the maximum of cur over all days, which is what best tracks.
The mathematical heart: if cur[i − 1] < 0 then cur[i − 1] + nums[i] < nums[i], so a negative prefix can never help, and dropping it is always safe.
Why it is fast
One max and one addition per day:
work = n, memory = 2 numbers
A million days cost a million steps instead of hundreds of billions.
How to recognise it in an interview
Cues: "contiguous subarray", "maximum sum", "best stretch", "buy on one day and sell on a later day". The first thing to say: "track the best run ending here, restart when it goes negative." Maximum Subarray is the example above. Best Time to Buy and Sell Stock is Kadane on the day-to-day price differences, since a buy-sell profit is the sum of consecutive differences. Maximum Product Subarray needs the same idea with two runs, best and worst, because a negative times a negative becomes the new best.
02The idea in one line
best ending at i = max(nums[i], best ending at i−1 + nums[i]).
Reach for it when: Maximum subarray, max product subarray, max circular subarray, stock I.
Cost: O(n) time, O(1) space.
03The walk-through, step by step
The video works one example: Maximum Subarray on [-2,1,-3,4,-1,2,1,-5,4]. These are its captions in order, so you can follow without the video or check a step you missed.
- cur = max(nums[i], cur + nums[i]): extend the run or restart at i
- i = 0: start, cur = -2, best = -2
- i = 1: restart, cur = 1, best = 1
- i = 2: extend, cur = -2
- i = 3: restart, cur = 4, best = 4
- i = 4: extend, cur = 3
- i = 5: extend, cur = 5, best = 5
- i = 6: extend, cur = 6, best = 6
- i = 7: extend, cur = 1
- i = 8: extend, cur = 5
- Best = 6 from the run [4,−1,2,1]
- A negative prefix never helps, so drop it: O(n) time, O(1) space
04Asked at Google
7 Google-tagged LeetCode problems use this pattern. Frequency is LeetCode's Google-tag score (higher means reported more often); "last 6 months" marks problems reported in the most recent window of the export.
| Problem | Difficulty | Frequency | Reported |
|---|---|---|---|
| Best Time to Buy and Sell Stock | Easy | 72 | last 6 months |
| Maximum Subarray | Medium | 65 | last 6 months |
| Maximum Product Subarray | Medium | 51 | last 6 months |
| Maximum Sum Circular Subarray | Medium | 26 | last 6 months |
| Best Sightseeing Pair | Medium | 20 | last 6 months |
| Maximum Absolute Sum of Any Subarray | Medium | 18 | last 6 months |
| Longest Turbulent Subarray | Medium | 6 |
05Related patterns
Other patterns in Dynamic Programming: 1-D linear DP, Longest increasing subsequence, Grid path DP, 0/1 knapsack (subset sum), Unbounded knapsack (coin change), Two-sequence DP (LCS / edit distance), Wildcard / regex matching DP, Palindromes: expand or table, Prefix segmentation DP, Interval DP (last operation), State-machine DP (stocks), Bitmask DP over subsets, Memoised DFS (top-down), Digit DP, Game DP (minimax on a range), Counting ways with modulo, DP over sorted keys (chains).