Max Product Subarray
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given an integer array nums, find the non-empty contiguous subarray whose elements have the largest product, and return that product.
Constraints: 1 ≤ n ≤ 2 × 10^4, -10 ≤ nums[i] ≤ 10, and the product of any subarray is guaranteed to fit in a 32-bit signed integer.
Example 1
nums = [2, 3, -2, 4]
answer = 6 // [2, 3]; every subarray that contains the lone -2 is negative
Example 2
nums = [-2, 0, -1]
answer = 0 // [-2, -1] is not contiguous, so the single 0 is the best choice
Example 3
nums = [-2, 3, -4]
answer = 24 // the whole array: the two negatives cancel
What makes this harder than the maximum sum subarray is the sign. A large negative product is bad now but becomes the best possible value as soon as another negative number arrives.
Approach 1: Brute force, every subarray
Intuition. Try every subarray. Fixing the start i and extending the end j one step at a time lets us update the product with a single multiplication, so we never recompute a product from scratch. (Recomputing each product separately would cost O(n^3).)
Algorithm.
- For every start
i: setprod = 1. - For every end
j ≥ i:prod *= nums[j], then updatebest. - Return
best.
best starts at Integer.MIN_VALUE, not 0: for [-2] the only subarray has product -2.
static int bruteForce(int[] nums) {
int best = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
int prod = 1;
for (int j = i; j < nums.length; j++) {
prod *= nums[j]; // product of nums[i..j]
best = Math.max(best, prod);
}
}
return best;
}
Complexity. Time O(n^2): about 2 × 10^8 multiplications for n = 2 × 10^4, too slow. Space O(1).
Approach 2: Optimal, prefix and suffix products
Intuition. Two observations remove almost all candidates.
- Zeros split the array. Any subarray containing a zero has product
0, so the interesting subarrays live inside the zero-free blocks between zeros, and0itself is one more candidate. - Inside a zero-free block, wider is never smaller in absolute value, because every element is a non-zero integer with
|x| ≥ 1. So we want the widest stretch whose sign is positive. If the block has an even number of negatives, the whole block is positive and best. If it has an odd number, we must drop exactly one negative: either the last negative and everything after it are cut away (leaving a prefix), or the first negative and everything before it are cut away (leaving a suffix).
Either way, the best subarray is a prefix or a suffix of some zero-free block. Scanning left to right with a running product gives every prefix; scanning right to left gives every suffix. When a running product hits 0, the block ended, so the next element starts a new product from 1.
Algorithm.
prefix = suffix = 1,best = MIN_VALUE.- For
i = 0 .. n - 1: reset either product to1if it is0; multiplyprefixbynums[i]andsuffixbynums[n - 1 - i]; updatebestwith both.
static int prefixSuffix(int[] nums) {
int n = nums.length, best = Integer.MIN_VALUE, prefix = 1, suffix = 1;
for (int i = 0; i < n; i++) {
if (prefix == 0) prefix = 1; // a zero ends the block; start a new one
if (suffix == 0) suffix = 1;
prefix *= nums[i];
suffix *= nums[n - 1 - i];
best = Math.max(best, Math.max(prefix, suffix));
}
return best;
}
Dry run on nums = [2, -5, -2, -4, 3] (three negatives, so one must go):
| i | nums[i] | prefix | nums[n-1-i] | suffix | best |
|---|---|---|---|---|---|
| 0 | 2 | 2 | 3 | 3 | 3 |
| 1 | -5 | -10 | -4 | -12 | 3 |
| 2 | -2 | 20 | -2 | 24 | 24 |
| 3 | -4 | -80 | -5 | -120 | 24 |
| 4 | 3 | -240 | 2 | -240 | 24 |
The answer 24 is the suffix [-2, -4, 3], which drops the first negative.
Complexity. Time O(n), one pass. Space O(1). Observation 2 depends on the values being integers; with fractions such as 0.5, widening can shrink the product, and only the next approach still works.
Approach 3: Optimal, Kadane-style DP with a maximum and a minimum
Intuition. Kadane’s algorithm for sums keeps the best sum of a subarray ending at the current index. For products that single value is not enough. The smallest (most negative) product ending at i - 1, multiplied by a negative nums[i], may become the largest. So keep two states:
maxEnd= largest product of a subarray ending ati;minEnd= smallest product of a subarray ending ati.
Every subarray ending at i is either [nums[i]] alone or a subarray ending at i - 1 extended by nums[i]. If x = nums[i] ≥ 0, multiplying by x keeps the order, so the largest extension is maxEnd · x and the smallest is minEnd · x. If x < 0, the order flips: the largest extension is minEnd · x. Swapping the two values before multiplying handles both cases with the same two lines. A zero sets both states to 0, which restarts the scan automatically.
Algorithm.
maxEnd = minEnd = best = nums[0].- For each later
x: ifx < 0, swapmaxEndandminEnd. ThenmaxEnd = max(x, maxEnd · x),minEnd = min(x, minEnd · x), andbest = max(best, maxEnd).
static int minMaxDp(int[] nums) {
int maxEnd = nums[0], minEnd = nums[0], best = nums[0];
for (int i = 1; i < nums.length; i++) {
int x = nums[i];
if (x < 0) { // multiplying by a negative swaps largest and smallest
int t = maxEnd;
maxEnd = minEnd;
minEnd = t;
}
maxEnd = Math.max(x, maxEnd * x); // either start fresh at x or extend
minEnd = Math.min(x, minEnd * x);
best = Math.max(best, maxEnd);
}
return best;
}
Dry run on Example 1, nums = [2, 3, -2, 4]:
| i | x | swap? | maxEnd | minEnd | best |
|---|---|---|---|---|---|
| 0 | 2 | start | 2 | 2 | 2 |
| 1 | 3 | no | max(3, 6) = 6 | min(3, 6) = 3 | 6 |
| 2 | -2 | yes, max = 3, min = 6 | max(-2, -6) = -2 | min(-2, -12) = -12 | 6 |
| 3 | 4 | no | max(4, -8) = 4 | min(4, -48) = -48 | 6 |
Complexity. Time O(n), space O(1). This version does not rely on |x| ≥ 1, so it also works for real numbers.
Edge cases and pitfalls
- Single negative element. Never start
bestat0;[-2]must return-2. - Zeros.
[-2, 0]returns0, and[0, 0]returns0. The prefix/suffix scan must reset after a zero, otherwise every later product stays0. - Odd number of negatives.
[-3, -1, -1]returns3: drop one end, never a middle element. - Overflow. The constraint promises that every subarray product fits in an
int, and every intermediate value in all three methods is the product of some subarray. Without that promise, uselong, ordoubleif values are fractional. - Empty input is not allowed;
minMaxDpreadsnums[0]directly.
Related problems
- Maximum Product Subarray in an Array is the A2Z sheet’s copy of the same problem.
- Kadane’s Algorithm is the sum version that Approach 3 generalises.
- Kadane’s Algorithm, maximum subarray sum is the Blind 75 entry for the sum version.
- Product of Array Except Self uses the same prefix and suffix product idea.