Problem
Given an array of integers, find the contiguous subarray that produces the largest product and return that product. Unlike the maximum sum subarray, a negative number can flip a tiny product into a huge one โ but only if there's another negative later to flip it back.
- Input:
nums = [2, 3, -2, 4] - Output:
6 - Explanation: The subarray
[2, 3]yields the largest product; extending to-2or4makes it smaller.
Counter-example: [-2, 0, -1] returns 0 โ the zero breaks any subarray that might span both negatives, so the best we can do is an empty product of 0.
Intuition
The key difference from maximum sum subarray is that negatives can be redeemed: -6 ร -4 = 24, so a deeply negative running product can suddenly become the largest if we hit another negative. This means tracking only the running maximum is not enough โ we must also track the running minimum, because the minimum (most negative) product can flip into the new maximum the moment we multiply by a negative number.
Approach 1 โ Brute Force
Try every possible subarray by iterating over all start positions, then extending to each end position and accumulating the product.
- Initialize
max_producttonums[0](not 0 โ the array can be all negative). - For each starting index, reset the running product to 1.
- For each ending index from the start onward, multiply in the current element.
- Update
max_productif the running product is larger. - Return
max_product.
1def maxProduct(nums):
2 max_product = nums[0]
3 for start in range(len(nums)):
4 running_product = 1
5 for end in range(start, len(nums)):
6 running_product *= nums[end]
7 max_product = max(max_product, running_product)
8 return max_productTime: O(nยฒ) โ two nested loops over all subarray boundaries.
Space: O(1) โ only a handful of variables.
Approach 2 โ Track Max and Min Simultaneously
At each position the best subarray ending here is one of three things: the element alone (start fresh), the element times the previous maximum, or the element times the previous minimum (since negative ร negative = positive). Track all three and pick the best.
- Initialize
max_product,current_max, andcurrent_minall tonums[0]. - For each subsequent element, compute three candidates: the element alone, element ร
current_max, element รcurrent_min. - Capture all candidates before overwriting โ both updates need the old values.
- Set
current_maxto the largest candidate andcurrent_minto the smallest. - Update
max_productwithcurrent_maxand return it at the end.
1def maxProduct(nums):
2 max_product = nums[0]
3 current_max = nums[0]
4 current_min = nums[0]
5
6 for num in nums[1:]:
7 # Compute all three candidates before overwriting anything
8 candidates = (num, current_max * num, current_min * num)
9 current_max = max(candidates)
10 current_min = min(candidates)
11 max_product = max(max_product, current_max)
12
13 return max_productTime: O(n) โ a single pass through the array.
Space: O(1) โ three variables regardless of input size.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Brute Force | O(nยฒ) | O(1) | Tiny inputs or when you need a simple verifier to test against |
| Track Max and Min | O(n) | O(1) | Always โ same space cost, far faster |
Common Mistakes
- Updating
current_maxbefore computingcurrent_minโ both calculations need the previous values of each other. If you writecurrentMax = ...first in Java or C++,currentMinwill silently multiply by the freshly updatedcurrentMax, producing the wrong answer. Always snapshot or compute all candidates together first. - Only tracking the maximum, not the minimum โ without
current_min, you'd miss cases like[-3, -4]where the minimum running product (-3) multiplied by -4 gives the true maximum (12). The entire trick of this problem lives in that second variable. - Assuming zeros are a special case to handle separately โ they're not. The "element alone" candidate naturally handles zeros:
max(0, prev_max ร 0, prev_min ร 0) = 0, so both trackers reset to 0 after a zero, which is exactly right. - Initializing
max_productto 0 โ the input can be entirely negative, making the answer the least-negative single element (e.g.,[-3, -5, -2]โ-2). Initializing to 0 would incorrectly return 0 for inputs with no non-negative elements. - Confusing this with maximum sum subarray โ Kadane's algorithm for sums resets to the current element whenever the running sum goes negative. Copying that logic here and discarding a negative product is wrong: the product could be redeemed by a later negative, which the
current_mintracker exists to capture.
Related Problems
maximum-subarrayโ the closest cousin: same single-pass DP structure but for sums, where Kadane's reset suffices because sums can't be "redeemed" like productsbest-time-to-buy-and-sell-stockโ also maintains a running best through one linear pass, tracking a global optimum updated at each stepsubarray-sum-equals-kโ another subarray running-total problem, solved with prefix sums rather than state trackinghouse-robberโ multi-state DP where each position depends on two previously computed values, paralleling the max/min dual-state herelongest-subarray-of-1s-after-deleting-one-elementโ sliding window that tracks extra auxiliary state alongside the main window, same mental model as the dual tracker