Builds on Kadane's Algorithm, which you haven’t finished yet.
Kadane's Algorithm with a Product (Maximum Product Subarray)
hi and lo, the largest and smallest product of a subarray that ends here.You get an integer array nums. A subarray is a run of numbers that sit next to each other. Multiply the numbers of a subarray to get its product.
Find the subarray with the largest product and return that product. The array can hold negative numbers and zeros. The subarray must hold at least one number.
[2, 3] has product 6. No other subarray is larger: -2 makes any run that holds it smaller, and [4] is only 4.Maximum Product Subarray
hi, lo = largest and smallest product ending at x ➔ a negative x swaps their roles ➔ keep the best hi| 1 | def max_product(nums: list[int]) -> int: |
| 2 | """Maximum Product Subarray: the best product of one unbroken run, found in a single pass. |
| 3 | Time: O(N) | Space: O(1) |
| 4 | """ |
| 5 | # hi, lo: the largest and smallest product of a subarray that ends at the current number; best: the largest hi seen so far |
| 6 | hi = lo = best = nums[0] |
| 7 | for x in nums[1:]: |
| 8 | # 1. Try x Alone, hi * x And lo * x: |
| 9 | # A negative x turns the smallest product into the largest, so both ends are extended. Both use the old hi and lo. |
| 10 | hi, lo = max(x, hi * x, lo * x), min(x, hi * x, lo * x) |
| 11 | |
| 12 | # 2. Track The Best: |
| 13 | # Every subarray ends at some index, so the largest hi seen at any index is the answer. |
| 14 | best = max(best, hi) |
| 15 | |
| 16 | return best |
x, keep both the largest and the smallest product: hi is the largest and lo is the smallest product of a subarray that ends at x, and best is the largest hi seen so far.A negative x turns the smallest product that ends before it into the largest, so keep both ends: hi, lo = max(x, hi * x, lo * x), min(x, hi * x, lo * x). Every subarray that ends at x is x alone or an earlier subarray extended by x, so these three candidates cover all of them, and best keeps the largest hi seen. The trap is the order: compute hi and lo in one assignment from the old values, because if hi changes first, lo is computed from a different subarray.
See it run
Maximum Product Subarray, step by step
hi -2lo -2best -2
Each column is one index of nums. The rows hi and lo fill in as the steps run.
xthe number being readhithe largest product of a run that ends at xlothe smallest product of a run that ends at x
Start: hi = lo = best = nums[0] = -2. Only one subarray ends at index 0.
Gotcha 1 of 3
- The trap
Gotcha 1: Keeping only the largest product
Mistakecurr = max(x, curr * x)Fixhi, lo = max(x, hi * x, lo * x), min(x, hi * x, lo * x)What happens: A negative
xturns the smallest product into the largest one. With only the largest product, the smallest product is lost, so a pair of negative numbers never combines into a positive product.Example: Maximum Product Subarray (LC 152): nums = [-2, 3, -4]Mistake: returns 3Fix: returns 24
- The best run is a sum, and adding a number never swaps the smallest and the largest, so one running value
curris enough, as in Maximum Subarray (LC 53). See Kadane's Algorithm
Deep Dive
Why the smallest product must be kept
In Kadane's Algorithm, a larger earlier value always gives a larger new value. A product does not follow this rule. Multiplying by a negative x reverses the order: the smallest earlier product becomes the largest new product.
So the state has two values for the subarrays that end at the current number. hi is the largest product and lo is the smallest product. lo is often negative, and one more negative x turns it into the next hi.
Example: for nums = [-2, 3, -4], after 3 the pair is hi = 3 and lo = -6. Then x = -4 gives lo * x = 24. The answer is 24. Without lo, the answer would be 3.
Why three candidates cover every subarray
A subarray that ends at x is x alone, or an earlier subarray that ends just before x, followed by x.
For the second form, the product is p * x, where p is the product of the earlier subarray. If x > 0, the largest result comes from the largest p, which is hi. If x < 0, it comes from the smallest p, which is lo. If x = 0, every result is 0.
So max(x, hi * x, lo * x) is the largest product that ends at x, and min(x, hi * x, lo * x) is the smallest. The tuple assignment computes both from the old hi and lo.
A zero resets the state. After x = 0, both hi and lo are 0. The next number starts a new run through the candidate x alone.
Trace and the cost
Trace for nums = [2, 3, -2, 4], as x, hi, lo, best:
2, 2, 2, 2 (the start).3, 6, 3, 6.-2, -2, -12, 6.4, 4, -48, 6.
The answer is 6, from [2, 3]. Trace for nums = [-2, 0, -1, 3, -4]: best is 12, from the run [-1, 3, -4]. The zero ends the earlier run, and the next run starts at -1.
The time is O(N), with three products per number. The space is O(1).
Products grow fast. With 40 copies of 2, the result is 1,099,511,627,776. Python integers have no limit. In a language with 32-bit or 64-bit integers, check the size limit, which LeetCode states for each problem.
Two other ways to the same answer
A second method uses prefix products. Scan from the left with a running product, and again from the right. After a zero, restart the running product at 1. The answer is the largest product seen in either scan. The reason is that the best subarray either has an even number of negative numbers, or it drops a negative number at one end. One of the two scans reaches it. This gives the same results as max_product on random arrays with zeros and negative numbers.
The variant Maximum Length of Subarray With Positive Product (LC 1567) keeps lengths and not products. Keep pos, the longest run ending here with a positive product, and neg, the longest with a negative product. A negative x swaps pos and neg, and a zero resets both to 0. This is the same swap idea as hi and lo.
60-Second Pitch
"This is Kadane's Algorithm, but a product needs two values. A negative number turns the smallest product into the largest. So I keep hi and lo, the largest and smallest product of a subarray that ends at the current number. For each x, the new hi is max(x, hi * x, lo * x) and the new lo is min(x, hi * x, lo * x). I compute both from the old values in one assignment. A zero resets both, and x alone starts the next run. I keep the largest hi in best. The time is O(N) and the space is O(1)."
Common Questions
- Why does Kadane's Algorithm with one running value fail for products?
A negative number reverses the order of products. The smallest earlier product, which is a large negative number, becomes the largest product after you multiply by a negative
x. One value forgets it. Keepinglosolves this.- What happens when the array contains a zero?
Both
hiandlobecome 0, because every product that includes the zero is 0. On the next number, the candidatexalone starts a new run. For[-2, 0, -1], the answer is 0.- What is the time and space complexity?
O(N)time, with three products and two comparisons per number.O(1)space, because onlyhi,loandbestexist.- Is there another method that does not use
lo? Yes. Scan from the left with a running product and again from the right, restarting at 1 after a zero. Return the largest product seen. It also takes
O(N)time andO(1)space.- How does Maximum Length of Subarray With Positive Product (LC 1567) relate to this card?
It asks for a length, not a product. Keep the longest run with a positive product,
pos, and the longest with a negative product,neg. A negativexswaps them, and a zero resets both to 0. It is the same two-ended idea ashiandlo.
SWE Reasoning
Spot it
- The question asks for the best product of one unbroken run of an array.
- The numbers can be negative or zero.
- Kadane's rule
max(x, curr * x)fails on a small example such as[-2, 3, -4]. - A negative number can turn a very small product into a very large one.
- A zero ends every run that crosses it.
The invariant, step by step
- Before the loop,
hi,loandbestare allnums[0]. Only one subarray ends at index 0. - For the next number
x,hiis the largest andlois the smallest product of a subarray that ends just beforex. - Every subarray that ends at
xhas the productx,hi * xorlo * xfor the largest or smallest case. These three values are the only candidates. hi, lo = max(...), min(...)computes both new values from the oldhiandloin one assignment.best = max(best, hi)keeps the largesthiseen.- After the last number,
bestcovers every end index, so it is the answer.
Traps
- Keeping only
hi. Two negative numbers never combine. See the card's gotcha for[-2, 3, -4]. - Two separate assignments. The second line reads the new
hi. See the card's gotcha for[-2, -3, -4]. - Dropping
xfrom the candidates. After a zero, nothing can restart a run. - Starting
bestat 0. The subarray must hold at least one number. For[-3], the answer is -3, and a start at 0 returns 0. - Returning
loor the lasthi. The answer isbest, because the best run may end before the last number. For[2, 3, -2, 4], the lasthiis 4 and the answer is 6.
Practice
Builds on Kadane's Algorithm, which you haven’t finished yet.
Kadane's Algorithm with a Product (Maximum Product Subarray)
hi and lo, the largest and smallest product of a subarray that ends here.You get an integer array nums. A subarray is a run of numbers that sit next to each other. Multiply the numbers of a subarray to get its product.
Find the subarray with the largest product and return that product. The array can hold negative numbers and zeros. The subarray must hold at least one number.
[2, 3] has product 6. No other subarray is larger: -2 makes any run that holds it smaller, and [4] is only 4.Maximum Product Subarray
hi, lo = largest and smallest product ending at x ➔ a negative x swaps their roles ➔ keep the best hi| 1 | def max_product(nums: list[int]) -> int: |
| 2 | """Maximum Product Subarray: the best product of one unbroken run, found in a single pass. |
| 3 | Time: O(N) | Space: O(1) |
| 4 | """ |
| 5 | # hi, lo: the largest and smallest product of a subarray that ends at the current number; best: the largest hi seen so far |
| 6 | hi = lo = best = nums[0] |
| 7 | for x in nums[1:]: |
| 8 | # 1. Try x Alone, hi * x And lo * x: |
| 9 | # A negative x turns the smallest product into the largest, so both ends are extended. Both use the old hi and lo. |
| 10 | hi, lo = max(x, hi * x, lo * x), min(x, hi * x, lo * x) |
| 11 | |
| 12 | # 2. Track The Best: |
| 13 | # Every subarray ends at some index, so the largest hi seen at any index is the answer. |
| 14 | best = max(best, hi) |
| 15 | |
| 16 | return best |
x, keep both the largest and the smallest product: hi is the largest and lo is the smallest product of a subarray that ends at x, and best is the largest hi seen so far.A negative x turns the smallest product that ends before it into the largest, so keep both ends: hi, lo = max(x, hi * x, lo * x), min(x, hi * x, lo * x). Every subarray that ends at x is x alone or an earlier subarray extended by x, so these three candidates cover all of them, and best keeps the largest hi seen. The trap is the order: compute hi and lo in one assignment from the old values, because if hi changes first, lo is computed from a different subarray.
See it run
Maximum Product Subarray, step by step
hi -2lo -2best -2
Each column is one index of nums. The rows hi and lo fill in as the steps run.
xthe number being readhithe largest product of a run that ends at xlothe smallest product of a run that ends at x
Start: hi = lo = best = nums[0] = -2. Only one subarray ends at index 0.
Gotcha 1 of 3
- The trap
Gotcha 1: Keeping only the largest product
Mistakecurr = max(x, curr * x)Fixhi, lo = max(x, hi * x, lo * x), min(x, hi * x, lo * x)What happens: A negative
xturns the smallest product into the largest one. With only the largest product, the smallest product is lost, so a pair of negative numbers never combines into a positive product.Example: Maximum Product Subarray (LC 152): nums = [-2, 3, -4]Mistake: returns 3Fix: returns 24
- The best run is a sum, and adding a number never swaps the smallest and the largest, so one running value
curris enough, as in Maximum Subarray (LC 53). See Kadane's Algorithm
Deep Dive
Why the smallest product must be kept
In Kadane's Algorithm, a larger earlier value always gives a larger new value. A product does not follow this rule. Multiplying by a negative x reverses the order: the smallest earlier product becomes the largest new product.
So the state has two values for the subarrays that end at the current number. hi is the largest product and lo is the smallest product. lo is often negative, and one more negative x turns it into the next hi.
Example: for nums = [-2, 3, -4], after 3 the pair is hi = 3 and lo = -6. Then x = -4 gives lo * x = 24. The answer is 24. Without lo, the answer would be 3.
Why three candidates cover every subarray
A subarray that ends at x is x alone, or an earlier subarray that ends just before x, followed by x.
For the second form, the product is p * x, where p is the product of the earlier subarray. If x > 0, the largest result comes from the largest p, which is hi. If x < 0, it comes from the smallest p, which is lo. If x = 0, every result is 0.
So max(x, hi * x, lo * x) is the largest product that ends at x, and min(x, hi * x, lo * x) is the smallest. The tuple assignment computes both from the old hi and lo.
A zero resets the state. After x = 0, both hi and lo are 0. The next number starts a new run through the candidate x alone.
Trace and the cost
Trace for nums = [2, 3, -2, 4], as x, hi, lo, best:
2, 2, 2, 2 (the start).3, 6, 3, 6.-2, -2, -12, 6.4, 4, -48, 6.
The answer is 6, from [2, 3]. Trace for nums = [-2, 0, -1, 3, -4]: best is 12, from the run [-1, 3, -4]. The zero ends the earlier run, and the next run starts at -1.
The time is O(N), with three products per number. The space is O(1).
Products grow fast. With 40 copies of 2, the result is 1,099,511,627,776. Python integers have no limit. In a language with 32-bit or 64-bit integers, check the size limit, which LeetCode states for each problem.
Two other ways to the same answer
A second method uses prefix products. Scan from the left with a running product, and again from the right. After a zero, restart the running product at 1. The answer is the largest product seen in either scan. The reason is that the best subarray either has an even number of negative numbers, or it drops a negative number at one end. One of the two scans reaches it. This gives the same results as max_product on random arrays with zeros and negative numbers.
The variant Maximum Length of Subarray With Positive Product (LC 1567) keeps lengths and not products. Keep pos, the longest run ending here with a positive product, and neg, the longest with a negative product. A negative x swaps pos and neg, and a zero resets both to 0. This is the same swap idea as hi and lo.
60-Second Pitch
"This is Kadane's Algorithm, but a product needs two values. A negative number turns the smallest product into the largest. So I keep hi and lo, the largest and smallest product of a subarray that ends at the current number. For each x, the new hi is max(x, hi * x, lo * x) and the new lo is min(x, hi * x, lo * x). I compute both from the old values in one assignment. A zero resets both, and x alone starts the next run. I keep the largest hi in best. The time is O(N) and the space is O(1)."
Common Questions
- Why does Kadane's Algorithm with one running value fail for products?
A negative number reverses the order of products. The smallest earlier product, which is a large negative number, becomes the largest product after you multiply by a negative
x. One value forgets it. Keepinglosolves this.- What happens when the array contains a zero?
Both
hiandlobecome 0, because every product that includes the zero is 0. On the next number, the candidatexalone starts a new run. For[-2, 0, -1], the answer is 0.- What is the time and space complexity?
O(N)time, with three products and two comparisons per number.O(1)space, because onlyhi,loandbestexist.- Is there another method that does not use
lo? Yes. Scan from the left with a running product and again from the right, restarting at 1 after a zero. Return the largest product seen. It also takes
O(N)time andO(1)space.- How does Maximum Length of Subarray With Positive Product (LC 1567) relate to this card?
It asks for a length, not a product. Keep the longest run with a positive product,
pos, and the longest with a negative product,neg. A negativexswaps them, and a zero resets both to 0. It is the same two-ended idea ashiandlo.
SWE Reasoning
Spot it
- The question asks for the best product of one unbroken run of an array.
- The numbers can be negative or zero.
- Kadane's rule
max(x, curr * x)fails on a small example such as[-2, 3, -4]. - A negative number can turn a very small product into a very large one.
- A zero ends every run that crosses it.
The invariant, step by step
- Before the loop,
hi,loandbestare allnums[0]. Only one subarray ends at index 0. - For the next number
x,hiis the largest andlois the smallest product of a subarray that ends just beforex. - Every subarray that ends at
xhas the productx,hi * xorlo * xfor the largest or smallest case. These three values are the only candidates. hi, lo = max(...), min(...)computes both new values from the oldhiandloin one assignment.best = max(best, hi)keeps the largesthiseen.- After the last number,
bestcovers every end index, so it is the answer.
Traps
- Keeping only
hi. Two negative numbers never combine. See the card's gotcha for[-2, 3, -4]. - Two separate assignments. The second line reads the new
hi. See the card's gotcha for[-2, -3, -4]. - Dropping
xfrom the candidates. After a zero, nothing can restart a run. - Starting
bestat 0. The subarray must hold at least one number. For[-3], the answer is -3, and a start at 0 returns 0. - Returning
loor the lasthi. The answer isbest, because the best run may end before the last number. For[2, 3, -2, 4], the lasthiis 4 and the answer is 6.