Builds on Linear DP, which you haven’t finished yet.
Kadane's Algorithm (Maximum Subarray)
i, and each step either extends it or starts fresh at i.Maximum Subarray (LC 53)(opens leetcode.com)
You're given an integer array nums. A subarray is any non-empty run of elements sitting next to each other in the array, with no skipping.
Scan every possible subarray and return the largest sum any of them adds up to.
Kadane's Algorithm
dp[i] = best sum ending at i ➔ extend the run or start fresh at x ➔ keep the best dp[i] seen| 1 | def max_subarray(nums: list[int]) -> int: |
| 2 | """Maximum Subarray: the best sum of one unbroken run, found in a single pass. |
| 3 | Time: O(N) | Space: O(1) |
| 4 | """ |
| 5 | # curr: the best sum of a subarray that ends at the current number; best: the largest curr seen so far |
| 6 | curr = best = nums[0] |
| 7 | for x in nums[1:]: |
| 8 | # 1. Extend Or Start Fresh: |
| 9 | # A negative curr can only lower x, so the best subarray ending at x is x alone when x > curr + x. |
| 10 | curr = max(x, curr + x) |
| 11 | |
| 12 | # 2. Track The Best: |
| 13 | # Every subarray ends at some index, so the largest curr seen at any index is the answer. |
| 14 | best = max(best, curr) |
| 15 | |
| 16 | return best |
x, curr is the best sum of a subarray that ends at x, and best is the largest curr seen so far.A subarray that ends at x either extends the best subarray that ends just before x, or starts at x alone, so curr = max(x, curr + x): a negative curr can only lower x, so it is dropped. Every subarray ends at some index, so the largest curr seen at any index, kept in best, is the answer. The trap is the start value: set curr = best = nums[0], not 0, because the subarray must hold at least one number, and an array of only negative numbers has a negative answer.
See it run
Kadane's Algorithm, step by step
curr -2best -2
The tag x marks the number being read. It is always the last number of the current run.
runa number in the current run (the run that ends at x)besta number in the best run found so farbotha number in the current run and in the best run
Start: curr = best = nums[0] = -2. The only subarray that ends at index 0 is -2 itself.
Gotcha 1 of 4
- The trap
Gotcha 1: Starting
currandbestat 0Mistakecurr = best = 0Fixcurr = best = nums[0]What happens: Maximum Subarray (LC 53) must take at least one number. Starting
bestat 0 counts the empty subarray as an answer. When every number is negative,beststays 0. Start fromnums[0]and loop over the rest.Example: Maximum Subarray (LC 53): nums = [-3, -1, -2]Mistake: returns 0Fix: returns -1
- The target is an exact sum, not a best sum, and the numbers can be negative, so dropping
currnever finds the runs, as in Subarray Sum Equals K (LC 560): count the earlier prefix sums in a map. See Prefix Sum Map - The chosen numbers are separate and no two may touch, instead of one unbroken run, as in House Robber (LC 198). See House Robber
Deep Dive
Why curr = max(x, curr + x) is correct
A subarray is one unbroken run of the array. If every number is positive, the answer is the whole array, because each extra number raises the sum. Negative numbers make the problem hard, because the run must decide which numbers to keep.
Here dp[i] is the best sum of a subarray that ends exactly at index i. It is not the best sum in the first i numbers.
Look at a subarray that ends at x. It has only two forms. It is x alone, or it is a subarray that ends just before x, followed by x.
For the second form, the best choice is the best subarray that ends just before x. That value is curr. So the best sum ending at x is max(x, curr + x).
When curr is negative, curr + x is smaller than x, so the old run is dropped and a new run starts at x.
Why best is the answer
Every subarray ends at some index. The best subarray overall ends at one of them, and at that index curr already equals its sum.
So the largest curr over all indices is the answer. The variable best keeps that value.
This is why curr and best are two variables. curr can go down after a negative number. best never goes down.
Trace for nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]. The rows marked restart are the steps where curr drops the old run and starts again at x:
x | curr + x | curr after | best after | Step |
|---|---|---|---|---|
| -2 | start | -2 | -2 | first number |
| 1 | -1 | 1 | 1 | restart |
| -3 | -2 | -2 | 1 | extend |
| 4 | 2 | 4 | 4 | restart |
| -1 | 3 | 3 | 4 | extend |
| 2 | 5 | 5 | 5 | extend |
| 1 | 6 | 6 | 6 | extend |
| -5 | 1 | 1 | 6 | extend |
| 4 | 5 | 5 | 6 | extend |
The answer is 6, from the run [4, -1, 2, 1].
Two steps restart the run. At 1, curr becomes 1 and not -2 + 1 = -1, because the old run was negative. At 4, curr becomes 4 and not -2 + 4 = 2, for the same reason. These restarts are where Kadane's rule does its work.
What this card adds to Linear DP
Linear DP keeps dp[i - 1] and dp[i - 2]. Here the formula needs only dp[i - 1], which is curr. The second variable best is not part of the recurrence. It records the maximum over all states.
This is a general pattern. When the answer is the best of all dp[i] and not the last dp[i], keep a running maximum next to the rolling value.
The start is curr = best = nums[0]. The subarray must hold at least one number, so an empty run at 0 is not allowed. An array of only negative numbers has a negative answer. For [-3, -1, -2], the answer is -1.
The cost and the follow-ups
There are N states, and each does two max calls. The time is O(N) and the space is O(1).
The brute force checks every start and end, which is N * (N + 1) / 2 subarrays. For the nine numbers above, that is 45. The loop makes 8 passes.
Adding up each subarray from scratch costs O(N^3) in total. Keeping a running sum while the end moves right lowers it to O(N^2). Kadane's algorithm removes the loop over the start, so it is O(N).
To return the run and not only its sum, record start = i each time curr restarts at x. Record best_start and best_end each time best increases.
To find the best rectangle in a matrix of numbers, pick two rows r1 and r2. Add the numbers of each column from r1 to r2 into one array. The best run in that array is the best rectangle between those two rows, so run Kadane on it. There are O(rows^2) row pairs and each Kadane pass costs O(cols), so the total is O(rows^2 * cols).
For a circular array, as in Maximum Sum Circular Subarray (LC 918), the answer is the larger of two values. One is the normal Kadane result. The other is the total sum minus the smallest subarray sum. If every number is negative, return the normal result.
60-Second Pitch
"I define curr as the best sum of a subarray that ends at the current number. That subarray is either the number alone, or the earlier best run plus the number. So curr = max(x, curr + x). Every subarray ends somewhere, so the largest curr over all positions is the answer. I keep it in best. I start both from nums[0], because the subarray must contain at least one number. One pass gives O(N) time and O(1) space."
Common Questions
- Why is it correct to drop the earlier run when it is negative?
A negative
currmakes every subarray that includes it smaller than the same subarray without it. Socurr + xis less thanx. Starting atxis always at least as good. This is whycurr = max(x, curr + x)is enough.- What does Kadane's algorithm return when all numbers are negative?
The largest single number. The start
curr = best = nums[0]and the comparison withxallow this. For[-3, -1, -2], the answer is -1. A version that starts at 0 returns 0, which is wrong, because the subarray must have at least one number.- How do you return the subarray and not only the sum?
Add
start. Setstart = iwhen the run restarts atx. Whenbestincreases, savebest_start = startandbest_end = i. For the LeetCode example, the saved run is[4, -1, 2, 1].- How does this change for Maximum Product Subarray (LC 152)?
Keep two values, the largest and the smallest product that end at
i. A negativexswaps their roles, because a negative number times the smallest product can become the largest. One value is not enough.- How do you solve Maximum Sum Circular Subarray (LC 918)?
The best run either stays inside the array, or wraps around. For a run that stays inside, use Kadane. For a run that wraps, take the total sum minus the smallest subarray sum, which is Kadane on the negated numbers. Return the larger value. If all numbers are negative, return the plain result, because the wrapping case would be an empty run.
SWE Reasoning
Spot it
- The question asks for the best sum of one unbroken run of an array.
- The run can start and end anywhere.
- The numbers can be negative, so the whole array is not always the best run.
- The brute force tries every start and end in O(N^2), and a one-pass answer is wanted.
- Every subarray can be described by where it ends. This suggests the state "best sum ending at
i".
The invariant, step by step
- Before the loop,
currandbestare bothnums[0]. The only subarray that ends at index 0 isnums[0]. - For the next number
x,curris the best sum of a subarray that ends just beforex. curr = max(x, curr + x)picks the better of two forms: extend the run, or start fresh atx.- Now
curris the best sum of a subarray that ends atx. best = max(best, curr)keeps the largestcurrseen so far.- After the last number,
bestcovers every end index, so it is the answer.
Traps
- Starting at 0.
curr = best = 0allows an empty subarray. For[-3, -1, -2], the result is 0 instead of -1. - Returning
curr.curris the best sum ending at the last number. For the LeetCode example, it is 5 and the answer is 6. - Comparing with 0.
curr = max(0, curr + x)starts an empty run. For[-2, -1], the result is 0 instead of -1. - Start index never moves. To return the run, set
start = ion every restart. Ifstartstays at 0, the run begins too early and includes numbers that lower the sum. - Wrong problem. A product, or an exact target sum, does not work with this rule. A negative number can turn a small product into a large one, and an exact sum needs a different method.
Practice
Builds on Linear DP, which you haven’t finished yet.
Kadane's Algorithm (Maximum Subarray)
i, and each step either extends it or starts fresh at i.Maximum Subarray (LC 53)(opens leetcode.com)
You're given an integer array nums. A subarray is any non-empty run of elements sitting next to each other in the array, with no skipping.
Scan every possible subarray and return the largest sum any of them adds up to.
Kadane's Algorithm
dp[i] = best sum ending at i ➔ extend the run or start fresh at x ➔ keep the best dp[i] seen| 1 | def max_subarray(nums: list[int]) -> int: |
| 2 | """Maximum Subarray: the best sum of one unbroken run, found in a single pass. |
| 3 | Time: O(N) | Space: O(1) |
| 4 | """ |
| 5 | # curr: the best sum of a subarray that ends at the current number; best: the largest curr seen so far |
| 6 | curr = best = nums[0] |
| 7 | for x in nums[1:]: |
| 8 | # 1. Extend Or Start Fresh: |
| 9 | # A negative curr can only lower x, so the best subarray ending at x is x alone when x > curr + x. |
| 10 | curr = max(x, curr + x) |
| 11 | |
| 12 | # 2. Track The Best: |
| 13 | # Every subarray ends at some index, so the largest curr seen at any index is the answer. |
| 14 | best = max(best, curr) |
| 15 | |
| 16 | return best |
x, curr is the best sum of a subarray that ends at x, and best is the largest curr seen so far.A subarray that ends at x either extends the best subarray that ends just before x, or starts at x alone, so curr = max(x, curr + x): a negative curr can only lower x, so it is dropped. Every subarray ends at some index, so the largest curr seen at any index, kept in best, is the answer. The trap is the start value: set curr = best = nums[0], not 0, because the subarray must hold at least one number, and an array of only negative numbers has a negative answer.
See it run
Kadane's Algorithm, step by step
curr -2best -2
The tag x marks the number being read. It is always the last number of the current run.
runa number in the current run (the run that ends at x)besta number in the best run found so farbotha number in the current run and in the best run
Start: curr = best = nums[0] = -2. The only subarray that ends at index 0 is -2 itself.
Gotcha 1 of 4
- The trap
Gotcha 1: Starting
currandbestat 0Mistakecurr = best = 0Fixcurr = best = nums[0]What happens: Maximum Subarray (LC 53) must take at least one number. Starting
bestat 0 counts the empty subarray as an answer. When every number is negative,beststays 0. Start fromnums[0]and loop over the rest.Example: Maximum Subarray (LC 53): nums = [-3, -1, -2]Mistake: returns 0Fix: returns -1
- The target is an exact sum, not a best sum, and the numbers can be negative, so dropping
currnever finds the runs, as in Subarray Sum Equals K (LC 560): count the earlier prefix sums in a map. See Prefix Sum Map - The chosen numbers are separate and no two may touch, instead of one unbroken run, as in House Robber (LC 198). See House Robber
Deep Dive
Why curr = max(x, curr + x) is correct
A subarray is one unbroken run of the array. If every number is positive, the answer is the whole array, because each extra number raises the sum. Negative numbers make the problem hard, because the run must decide which numbers to keep.
Here dp[i] is the best sum of a subarray that ends exactly at index i. It is not the best sum in the first i numbers.
Look at a subarray that ends at x. It has only two forms. It is x alone, or it is a subarray that ends just before x, followed by x.
For the second form, the best choice is the best subarray that ends just before x. That value is curr. So the best sum ending at x is max(x, curr + x).
When curr is negative, curr + x is smaller than x, so the old run is dropped and a new run starts at x.
Why best is the answer
Every subarray ends at some index. The best subarray overall ends at one of them, and at that index curr already equals its sum.
So the largest curr over all indices is the answer. The variable best keeps that value.
This is why curr and best are two variables. curr can go down after a negative number. best never goes down.
Trace for nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]. The rows marked restart are the steps where curr drops the old run and starts again at x:
x | curr + x | curr after | best after | Step |
|---|---|---|---|---|
| -2 | start | -2 | -2 | first number |
| 1 | -1 | 1 | 1 | restart |
| -3 | -2 | -2 | 1 | extend |
| 4 | 2 | 4 | 4 | restart |
| -1 | 3 | 3 | 4 | extend |
| 2 | 5 | 5 | 5 | extend |
| 1 | 6 | 6 | 6 | extend |
| -5 | 1 | 1 | 6 | extend |
| 4 | 5 | 5 | 6 | extend |
The answer is 6, from the run [4, -1, 2, 1].
Two steps restart the run. At 1, curr becomes 1 and not -2 + 1 = -1, because the old run was negative. At 4, curr becomes 4 and not -2 + 4 = 2, for the same reason. These restarts are where Kadane's rule does its work.
What this card adds to Linear DP
Linear DP keeps dp[i - 1] and dp[i - 2]. Here the formula needs only dp[i - 1], which is curr. The second variable best is not part of the recurrence. It records the maximum over all states.
This is a general pattern. When the answer is the best of all dp[i] and not the last dp[i], keep a running maximum next to the rolling value.
The start is curr = best = nums[0]. The subarray must hold at least one number, so an empty run at 0 is not allowed. An array of only negative numbers has a negative answer. For [-3, -1, -2], the answer is -1.
The cost and the follow-ups
There are N states, and each does two max calls. The time is O(N) and the space is O(1).
The brute force checks every start and end, which is N * (N + 1) / 2 subarrays. For the nine numbers above, that is 45. The loop makes 8 passes.
Adding up each subarray from scratch costs O(N^3) in total. Keeping a running sum while the end moves right lowers it to O(N^2). Kadane's algorithm removes the loop over the start, so it is O(N).
To return the run and not only its sum, record start = i each time curr restarts at x. Record best_start and best_end each time best increases.
To find the best rectangle in a matrix of numbers, pick two rows r1 and r2. Add the numbers of each column from r1 to r2 into one array. The best run in that array is the best rectangle between those two rows, so run Kadane on it. There are O(rows^2) row pairs and each Kadane pass costs O(cols), so the total is O(rows^2 * cols).
For a circular array, as in Maximum Sum Circular Subarray (LC 918), the answer is the larger of two values. One is the normal Kadane result. The other is the total sum minus the smallest subarray sum. If every number is negative, return the normal result.
60-Second Pitch
"I define curr as the best sum of a subarray that ends at the current number. That subarray is either the number alone, or the earlier best run plus the number. So curr = max(x, curr + x). Every subarray ends somewhere, so the largest curr over all positions is the answer. I keep it in best. I start both from nums[0], because the subarray must contain at least one number. One pass gives O(N) time and O(1) space."
Common Questions
- Why is it correct to drop the earlier run when it is negative?
A negative
currmakes every subarray that includes it smaller than the same subarray without it. Socurr + xis less thanx. Starting atxis always at least as good. This is whycurr = max(x, curr + x)is enough.- What does Kadane's algorithm return when all numbers are negative?
The largest single number. The start
curr = best = nums[0]and the comparison withxallow this. For[-3, -1, -2], the answer is -1. A version that starts at 0 returns 0, which is wrong, because the subarray must have at least one number.- How do you return the subarray and not only the sum?
Add
start. Setstart = iwhen the run restarts atx. Whenbestincreases, savebest_start = startandbest_end = i. For the LeetCode example, the saved run is[4, -1, 2, 1].- How does this change for Maximum Product Subarray (LC 152)?
Keep two values, the largest and the smallest product that end at
i. A negativexswaps their roles, because a negative number times the smallest product can become the largest. One value is not enough.- How do you solve Maximum Sum Circular Subarray (LC 918)?
The best run either stays inside the array, or wraps around. For a run that stays inside, use Kadane. For a run that wraps, take the total sum minus the smallest subarray sum, which is Kadane on the negated numbers. Return the larger value. If all numbers are negative, return the plain result, because the wrapping case would be an empty run.
SWE Reasoning
Spot it
- The question asks for the best sum of one unbroken run of an array.
- The run can start and end anywhere.
- The numbers can be negative, so the whole array is not always the best run.
- The brute force tries every start and end in O(N^2), and a one-pass answer is wanted.
- Every subarray can be described by where it ends. This suggests the state "best sum ending at
i".
The invariant, step by step
- Before the loop,
currandbestare bothnums[0]. The only subarray that ends at index 0 isnums[0]. - For the next number
x,curris the best sum of a subarray that ends just beforex. curr = max(x, curr + x)picks the better of two forms: extend the run, or start fresh atx.- Now
curris the best sum of a subarray that ends atx. best = max(best, curr)keeps the largestcurrseen so far.- After the last number,
bestcovers every end index, so it is the answer.
Traps
- Starting at 0.
curr = best = 0allows an empty subarray. For[-3, -1, -2], the result is 0 instead of -1. - Returning
curr.curris the best sum ending at the last number. For the LeetCode example, it is 5 and the answer is 6. - Comparing with 0.
curr = max(0, curr + x)starts an empty run. For[-2, -1], the result is 0 instead of -1. - Start index never moves. To return the run, set
start = ion every restart. Ifstartstays at 0, the run begins too early and includes numbers that lower the sum. - Wrong problem. A product, or an exact target sum, does not work with this rule. A negative number can turn a small product into a large one, and an exact sum needs a different method.