HARD Lab · Dynamic Programming · 29

152. Maximum Product Subarray

Unlike sum, a product can explode positive via two negatives — so you must track both the running maximum and minimum product ending at each index, because a negative number turns the worst product into the best.

MediumKadaneMin/Max TrackingTypeScript

PROBLEM What we're solving

Given an integer array nums, return the largest product of any contiguous subarray. For [2, 3, -2, 4] the answer is 6 (subarray [2, 3]). For [-2, 0, -1] the answer is 0 because the zero isolates both negatives and no subarray of just [-2, -1] is contiguous.

KEY IDEA Carry the min to catch the sign-flip

Insight → the standard Kadane trick of keeping only the running max breaks the moment you hit a negative number — that negative might multiply the most negative running product into the new maximum. So at every position you must track boththe max product ending here and the min product ending here. A single negative element flips the two: yesterday's worst becomes today's best.

RECIPE Seed, scan, swap candidates

  • 0 · Seed. Initialize curMax, curMin, and best all to nums[0]. Handles the single-element edge case automatically.
  • 1 · Scan from index 1. For each nums[i], compute three candidates: nums[i] alone (start a fresh subarray), curMax * nums[i], and curMin * nums[i].
  • 2 · Update max and min. New curMax = the largest of the three; new curMin = the smallest. Use a temp variable — both updates must read the old curMax/curMin.
  • 3 · Extend best. best = Math.max(best, curMax). Only the max can beat the running answer.
  • 4 · Return best. One linear pass, O(1) extra space.
Classic confusion → forgetting to use a temporary when updating curMax and curMin. If you write curMax = Math.max(..., curMax*n, curMin*n) and then curMin = Math.min(..., curMax*n, ...), the second line reads the already-updated curMax — producing a subtly wrong answer. Always compute tmpMax and tmpMin from the same old values, then assign both.

COST Complexity & alternatives

Try every subarray
O(n²)
Two nested loops; product computed inline.
Kadane + min tracking
O(n)
One pass; O(1) space — three scalars only.

Prefix-product approach

An alternative is to keep running left-to-right prefix products and right-to-left prefix products, taking the max over all positions. It works because a zero "resets" the window from both sides. The min/max approach above is more intuitive and slightly simpler to explain.

Pattern transfer → the min/max carrier is the same trick in Maximum Product of Three Numbers, Best Time to Buy and Sell Stock with Cooldown(where the penalty state is the "min" carrier), and any DP where a sign change can invert the optimal sub-structure. Whenever a Kadane-style scan encounters sign flips, ask "do I need to carry the opposite extreme?"

RUN IT Track both max and min — a negative flips them

step 0 / 8
STARTInput: [2, 3, -2, 4]. We track both the running max and min product ending here — a negative number can flip the min into a new max.
2031-2243
State
curMax
curMin
best
current index (positive)current index (negative — sign-flip!)curMaxcurMin
slowfast

TYPESCRIPT The solution, annotated

maxProduct.ts
function maxProduct(nums: number[]): number {
  let curMax = nums[0];
  let curMin = nums[0];
  let best   = nums[0];

  for (let i = 1; i < nums.length; i++) {
    const n = nums[i];
    // A negative n flips curMin into the new curMax — compute all three candidates.
    const tmpMax = Math.max(n, curMax * n, curMin * n);
    const tmpMin = Math.min(n, curMax * n, curMin * n);
    curMax = tmpMax;
    curMin = tmpMin;
    best = Math.max(best, curMax);
  }

  return best;
}

Reading it block by block

Lines 2–4 — seed all three trackers. Starting at nums[0] handles single-element arrays (the answer is just that element) and gives a valid base for the loop that follows.
Lines 6–8 — compute three candidates. At each position the subarray either starts fresh (n alone), extends the previous maximum (curMax * n), or extends the previous minimum (curMin * n) — which is crucial when n is negative. Storing in tmpMax / tmpMin before assignment prevents a data-hazard where the updated curMax contaminates the curMin computation.
Lines 9–10 — commit the new max and min. Both reads used the old values (via tmps), so the update is safe even if you write them in either order.
Line 11 — extend best. Only curMax can beat the global record; curMin is kept only to seed the next iteration.
Line 15 — return best. One pass, three scalars. The loop never looks back; all history is captured in curMax and curMin.
Complexity → O(n) time — one pass; O(1) space — three scalar variables regardless of input size. The inner three-way max/min is constant work per element.

INTERVIEWFollow-ups they'll ask

  • "Return the actual subarray, not just its product." Track the start index alongside curMax; when curMax resets to n alone, record the new start. Keep a separate best-start/best-end pair updated whenever best improves.
  • "What if the array contains zeros?" A zero collapses curMax and curMinto 0, then the next element seeds a fresh window via the "n alone" candidate. The algorithm handles zeros automatically — no special case needed.
  • "All negative numbers?"The answer is the product of the two leftmost negatives, or simply the largest single element (least negative) if there's only one. The three-candidate rule covers this: the min of the previous min times a negative yields the new max.
  • "Minimum product subarray?" Return best tracking curMin instead — symmetric by replacing every max with min and vice versa.
  • "What's the brute-force?" Two nested loops over all O(n²) start/end pairs, multiplying inline. O(n²) time, O(1) space — fine for a quick correctness check but fails on large inputs.

MNEMONIC The one-liner

"Carry both the best and the worst — one negative flip turns the worst into the best."

TRIGGERS When you see ___ → reach for ___

"maximum product of a subarray"Kadane + min/max dual tracker
subarray with negative numbers involved in productcarry curMin alongside curMax
Kadane breaks because of sign flipsadd the opposite extreme tracker
"contiguous subarray, multiply not add"seed curMax=curMin=nums[0], three candidates

SKELETON The reusable shape

skeleton.ts
let curMax = nums[0];
let curMin = nums[0];
let best   = nums[0];

for (let i = 1; i < nums.length; i++) {
  const n = nums[i];
  const tmpMax = Math.max(n, curMax * n, curMin * n);
  const tmpMin = Math.min(n, curMax * n, curMin * n);
  curMax = tmpMax;
  curMin = tmpMin;
  best = Math.max(best, curMax);
}

return best;

FLASHCARDS Tap to flip

Why does standard Kadane (max-only) fail for products?
A negative number flips the sign — the previous minimum product becomes the new maximum. You must carry curMin to catch this.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 7
QUESTION 01
For input [2, 3, -2, 4], what does the algorithm return?
QUESTION 02
Why must you track both curMax and curMin instead of just curMax?
QUESTION 03
What is the classic implementation bug with this pattern?
QUESTION 04
What is the time complexity of the optimal solution?
QUESTION 05
Given nums = [-2, 3, -4], trace the algorithm: what is the final best?
QUESTION 06
How does the algorithm handle an array like [-3, 0, 2]?
QUESTION 07
Space complexity of the optimal solution?