Maximum Product Subarray
Given an integer array nums, find a contiguous non-empty subarray that has the largest product, and return the product.
- 1 <= nums.length <= 2 × 10⁴
- -10 <= nums[i] <= 10
- The product of any subarray of nums is guaranteed to fit in a 32-bit integer
nums = [2,3,-2,4]6nums = [-2,0,-1]0nums = [-2,3,-4]24nums = [-2]-2Same setting as Maximum Subarray — choose a contiguous, non-empty slice — but now we multiply its elements and want the largest product. If Kadane's reasoning carried over unchanged, this page would be one line long. It does not, and pinpointing exactly where it breaks is the entire lesson. Multiplication has two behaviors that addition lacks: a negative number flips the sign, turning the largest value into the smallest and vice versa, and a zero annihilates, collapsing any window that contains it to 0.
As before, a subarray is a (start, end) choice, sliding the end extends the running product by one multiplication, and there are roughly N²/2 windows — so checking them all costs O(N²):
res = nums[0]
for i in range(len(nums)):
current_prod = 1
for j in range(i, len(nums)):
current_prod *= nums[j] # extend the running product by one number
res = max(res, current_prod)
return resTry to reuse yesterday's reframe directly: "track the best product ending at each position — either the element alone, or the previous best extended." Watch it fail on [-2, 3, -4]. The best product ending at the 3 is 3 (because -2 × 3 = -6 is worse). Carrying only that, at -4 we would choose max(-4, 3 × -4 = -12) = -4, and answer 3. The true answer is (-2) × 3 × (-4) = 24. What did we throw away? The worst product ending at the 3, which was -6. One step later, multiplying that miserable -6 by -4 produces 24 — the sign flip turned the worst product into the best. In sums, a deeply negative running total is irredeemable dead weight; Kadane's is right to dump it. In products, it is a coiled spring — one more negative number away from being enormous. Remembering only the best is simply not enough information.
So ask carefully: with x = nums[i], what can the best product ending at position i possibly be? Any subarray ending at i is x times some subarray ending at i - 1, or x alone. Multiplying by a positive x keeps the ranking of the previous products — the biggest stays biggest. Multiplying by a negative x reverses the ranking — the smallest previous product becomes the biggest. And x = 0 flattens everything to 0. So only three candidates can ever win: x alone, x × (best ending at i-1), and x × (worst ending at i-1). The identical three candidates (taking the minimum) cover the worst.
max_p = min_p = result = nums[0]
for i in range(1, len(nums)):
num = nums[i]
if num < 0:
max_p, min_p = min_p, max_p # a negative flips the ranking, so the roles swap
max_p = max(num, num * max_p) # best product ending here
min_p = min(num, num * min_p) # worst product ending here
result = max(result, max_p)
return resultThe swap line is the three-candidate rule in compressed form: when num is negative, it is the old minimum that can produce the new maximum, so the two trade places before the usual "extend or start fresh" choice runs.
Zeros need no special code, and seeing why is instructive. When num is 0, both updates collapse to max(0, 0) = 0 — which is the honest truth: every subarray ending at the zero has product 0. result records that 0, and that matters: in [-2, 0, -1] no positive product exists anywhere, and the correct answer is 0, achieved by the one-element window [0]. On the next element, the max(num, ...) choice naturally starts a fresh streak. So a zero quietly acts as a wall between product streaks — with no extra code at all.
One pass, O(N) time, O(1) extra space — the same cost as Kadane's, for the price of one extra tracked value. The lesson here is subtle and very transferable. The "best ending here" reframe survived the change of operation; what changed is what must be remembered. Addition never reorders past results, so one running value suffices. Multiplication can invert the entire ranking, so you must keep every past value that could still become optimal — here, exactly the two extremes. Whenever you adapt a known technique to a new setting, re-ask the question: "what information does the future actually need from the past?" Getting that answer wrong — not the coding — is where this problem defeats people.
The Sign Flipper
INSIGHT
Negatives are risky but valuable. They can turn the worst min into the best max instantly.
EFFICIENCY
We find the record in one single pass by keeping track of both extremes at every step.