Definition
- Maximum Product Subarray – finds the contiguous slice of an array whose element product is largest, requiring dual state because multiplication can flip signs.
- Running max and min –
maxProducttracks the best product ending here;minProducttracks the worst (most negative), which can become the best after a negative multiplier. - Swap on negative – when the current element is negative,
maxProductandminProductare swapped before the update so the sign flip routes values correctly. - O(n) time, O(1) space – a single pass with three scalar variables:
maxProduct,minProduct, andglobalBest. - Zero handling – a zero resets both running products naturally because
max(0, 0 * anything) = 0.
The National Bank of India tracks Rajesh's daily growth factors for a portfolio, numbers above one for gain days and below one or negative for reversal days, and an analyst wants the consecutive stretch whose multiplied effect is largest. The twist is that a run of two bad, negative, days multiplied together becomes positive and possibly huge, so you must remember not only the best running product but also the worst, because today's worst could become tomorrow's best after a sign flip.
Understanding the Concepts
What is the Maximum Product Subarray problem?
The Maximum Product Subarray problem asks for the contiguous subarray whose elements, when multiplied together, produce the largest product. Unlike the sum version, multiplication introduces a complication: a negative number can flip a very small (most negative) product into the largest product. This means tracking only the running maximum is not enough.
At the National Bank of India, Pankaj asks Rajesh to find the consecutive stretch of daily portfolio growth factors that produces the strongest compound effect, including reversal days where the factor is negative.
Why do you need both a running max and a running min?
In the additive Maximum Subarray (Kadane's), adding a negative value always reduces the sum, so you only need a running maximum. In the product version, multiplying a large negative running product by another negative element produces a large positive result. If you only tracked the running max, you would lose the large negative candidate, and when the next negative arrives you would miss the sign flip that creates the true maximum.
Example: [2, 3, -2, 4, -1]
Index: 0 1 2 3 4
maxP: 2 6 -2 4 -4 (tracking only max: misses flip)
minP: 2 3 -12 -48 48 (the min at index 2 becomes huge after two negatives)
gBest: 2 6 6 6 48 (correct answer: 48 from entire array product)
How does the algorithm work internally?
You keep three values: maxProduct (best product ending here), minProduct (worst product ending here), and globalBest (best seen anywhere). All start at the first element.
- For each remaining element:
- If the element is negative, swap
maxProductandminProductbefore updating. - Set
maxProduct = max(element, maxProduct * element). - Set
minProduct = min(element, minProduct * element). - Set
globalBest = max(globalBest, maxProduct).
The swap routes values correctly: a negative times the old max gives a small number, while the same negative times the old min (which may be negative) gives a large positive.
How do you implement it? (with code)
package com.bank.analysis;
import java.math.BigDecimal;
public class MaxProductSubarray {
public static BigDecimal maxProduct(BigDecimal[] factors) {
if (factors == null || factors.length == 0) {
throw new IllegalArgumentException("Array must not be empty");
}
BigDecimal maxP = factors[0];
BigDecimal minP = factors[0];
BigDecimal globalBest = factors[0];
for (int i = 1; i < factors.length; i++) {
// Swap on negative: roles of max and min flip
if (factors[i].signum() < 0) {
BigDecimal temp = maxP;
maxP = minP;
minP = temp;
}
// Extend or restart
maxP = factors[i].max(maxP.multiply(factors[i]));
minP = factors[i].min(minP.multiply(factors[i]));
// Update global best
globalBest = globalBest.max(maxP);
}
return globalBest;
}
public static void main(String[] args) {
BigDecimal[] dailyFactors = {
new BigDecimal("2"), new BigDecimal("3"),
new BigDecimal("-2"), new BigDecimal("4"),
new BigDecimal("-1")
};
System.out.println("Max product: " + maxProduct(dailyFactors));
}
}
Max product: 48
How is this different from Kadane's Algorithm (Maximum Sum)?
| Aspect | Maximum Sum (Kadane's) | Maximum Product |
|---|---|---|
| Operation | Addition | Multiplication |
| State needed | One running value (currentBest) | Two running values (maxP, minP) |
| Sign flip concern | No | Yes: negative times negative becomes positive |
| Swap step | Not needed | Swap maxP and minP when element is negative |
| Zero handling | No special case | Zero resets both products to zero naturally |
| Initialization | Both values to a[0] | All three values to a[0] |
// Kadane's (sum): one running value
BigDecimal cur = a[0], best = a[0];
for (int i = 1; i < n; i++) {
cur = a[i].max(cur.add(a[i]));
best = best.max(cur);
}
// Product: two running values + swap
BigDecimal maxP = a[0], minP = a[0], best = a[0];
for (int i = 1; i < n; i++) {
if (a[i].signum() < 0) { var t = maxP; maxP = minP; minP = t; }
maxP = a[i].max(maxP.multiply(a[i]));
minP = a[i].min(minP.multiply(a[i]));
best = best.max(maxP);
}
What happens with zeros in the array?
A zero resets both running products because any product through a zero is zero. Taking the maximum of the element alone (which is zero) and the element times a previous product (which is also zero) naturally restarts the run at the next element. No special code is needed.
// Array: [2, 3, 0, -2, 4]
// At index 2 (zero): maxP = max(0, 0) = 0, minP = min(0, 0) = 0
// The run restarts at index 3
What are the edge cases?
- Single element – return that element.
- All negative – if the count is even, the entire array product is positive and may be the answer; if odd, exclude one end element.
- Contains zeros – zeros split the array into segments; the answer is the best segment product or zero.
- All zeros – the answer is zero.
- Empty array – reject with an exception.
// All negative, even count: [-2, -3] → product = 6
// All negative, odd count: [-2, -3, -4] → best is [-2, -3] = 6 or [-3, -4] = 12
What about precision for financial data?
If the values are growth factors for a portfolio, you must use BigDecimal with multiply, max, and min via compareTo. Floating-point drift compounds dangerously over a long multiplied chain, and in banking this is unacceptable.
What are common interview follow-up questions?
- Why not just track the max product? – Because a negative times the most-negative product creates the largest positive.
- Can you return the actual subarray? – Yes, track start indices similar to Kadane's with index tracking.
- What if the array has only one negative? – The answer excludes the segment containing that negative, unless the rest is all zeros.
- Time and space? – O(n) time, O(1) space.
Visual Overview
┌──────────────────────────────────────────────────────┐ │ Maximum Product Subarray Flow │ ├──────────────────────────────────────────────────────┤ │ │ │ Initialize: maxP = a[0], minP = a[0], best = a[0] │ │ │ │ For each a[i] from index 1 to n-1: │ │ │ │ ┌──────────────────────┐ │ │ │ Is a[i] negative? │ │ │ └────┬─────────┬───────┘ │ │ Yes No │ │ │ │ │ │ ┌─────▼─────┐ │ │ │ │ Swap maxP │ │ │ │ │ and minP │ │ │ │ └─────┬─────┘ │ │ │ │ │ │ │ ┌────▼──────────▼────────────────┐ │ │ │ maxP = max(a[i], maxP * a[i]) │ │ │ │ minP = min(a[i], minP * a[i]) │ │ │ │ best = max(best, maxP) │ │ │ └────────────────────────────────┘ │ │ │ │ Return best │ └──────────────────────────────────────────────────────┘
Key Takeaway
Maximum Product Subarray finds the contiguous slice whose element product is largest, and the twist over the additive version is that multiplying by a negative number flips the sign, so the most negative running product is a hidden candidate that can become the winner after another negative. You solve it in a single linear pass with constant space by carrying both a running maximum product and a running minimum product ending at each position, plus a global best.
The key step is swapping the running max and min whenever the current element is negative, before you update them. A negative times the old maximum gives a small, often most negative, number, while the same negative times the old minimum gives a large positive, so the swap routes the large positive into the max and the small negative into the min. That is why keeping only a running max, which works for the additive problem, silently fails here.
You also handle zeros and precision cleanly. A zero naturally resets both running products because taking the maximum and minimum against the element alone, which is zero, restarts the run at the next element, so no special case is needed. For money you use BigDecimal with multiply, max, and min, which avoids the floating-point drift that would compound dangerously over a long multiplied chain.
In a banking project at the National Bank of India, this is used on Rajesh's daily portfolio growth factors, where reversal days are negative, to find the consecutive stretch whose compound multiplied effect was strongest, correctly rewarding runs where two reversals flipped into a large gain.
Remember one sentence: Track both running max and running min products, swap them on a negative element, and update the global best to solve Maximum Product Subarray in O(n) time.
---