Maximum Product Subarray
Medium
๐ญ Introduction
Youโre given an array of integers (which can be positive, negative, or zero). Your task is to find the contiguous subarray with the largest product.
Unlike the sum version, products behave differently with negative numbers!
A negative number multiplied by another negative becomes positive. This twist makes the problem more interesting and challenging.
Your goal is to find which contiguous subarray has the maximum product.
๐งฉ Problem Statement
LeetCode #152 โ Maximum Product Subarray
Given an integer array nums, find a contiguous non-empty subarray within the array that has the largest product, and return the product. The test cases are generated so that the answer will fit in a 32-bit integer.
๐ Example 1:
Input: nums = [2,3,-2,4]
Output: 6
Explanation:
The subarray [2,3] has the largest product = 6.
๐ Example 2:
Input: nums = [-2,0,-1]
Output: 0
Explanation:
The result cannot be 2, because [-2,-1] is not a subarray. The maximum product is 0 from the subarray [0].
๐ Example 3:
Input: nums = [-2,3,-4]
Output: 24
Explanation:
The subarray [-2,3,-4] has the largest product = 24. Notice how two negatives make a positive!
๐ง Step 1: Why Maximum Subarray Approach Fails
You might think: โJust use Kadaneโs Algorithm but with multiplication!โ
# This DOESN'T work!
def maxProduct(nums):
max_prod = current_prod = nums[0]
for num in nums[1:]:
current_prod = max(num, current_prod * num)
max_prod = max(max_prod, current_prod)
return max_prod
Why does this fail?
Consider nums = [2, -5, -2]:
- At index 1:
current_prod = max(-5, 2 * -5) = -5 - At index 2:
current_prod = max(-2, -5 * -2) = 10โ
But consider nums = [-2, 3, -4]:
- At index 1:
current_prod = max(3, -2 * 3) = 3 - At index 2:
current_prod = max(-4, 3 * -4) = -4โ
We missed the answer 24 because we lost track of the negative product -6 at index 1!
๐ค Step 2: The Key Insight
Hereโs the crucial observation:
A negative number can turn a small negative product into a large positive product!
We need to track both:
- Maximum product ending at current position
- Minimum product ending at current position (which might become maximum later!)
Why track minimum?
- A large negative number (minimum) ร negative number = large positive (maximum)
- Example:
-10 ร -5 = 50
๐ Step 3: The Two-Tracker Solution
At each position, we maintain:
max_prod: maximum product ending heremin_prod: minimum product ending here
For each new number, we have three choices:
- Start fresh with just the current number
- Multiply with previous maximum
- Multiply with previous minimum (important for negatives!)
class Solution:
def maxProduct(self, nums: List[int]) -> int:
if not nums:
return 0
max_prod = min_prod = result = nums[0]
for num in nums[1:]:
# When we multiply by negative, max becomes min and vice versa
if num < 0:
max_prod, min_prod = min_prod, max_prod
# Update max and min products
max_prod = max(num, max_prod * num)
min_prod = min(num, min_prod * num)
# Update global result
result = max(result, max_prod)
return result
Letโs trace through Example 3: [-2, 3, -4]
| i | num | Before swap | After swap | max_prod | min_prod | result |
|---|---|---|---|---|---|---|
| 0 | -2 | - | - | -2 | -2 | -2 |
| 1 | 3 | max=-2, min=-2 | (no swap) | max(3, -2ร3)=3 | min(3, -2ร3)=-6 | 3 |
| 2 | -4 | max=3, min=-6 | max=-6, min=3 | max(-4, -6ร-4)=24 | min(-4, 3ร-4)=-12 | 24 |
Final answer: 24
โฑ๏ธ Time Complexity: $O(n)$ โ Single pass through the array
๐ฆ Space Complexity: $O(1)$ โ Only a few variables
๐ฏ Step 4: Alternative Formulation (Without Swap)
We can avoid the swap by computing both possibilities at once:
class Solution:
def maxProduct(self, nums: List[int]) -> int:
if not nums:
return 0
max_prod = min_prod = result = nums[0]
for num in nums[1:]:
# Store previous values
temp_max = max_prod
# Compute new max and min considering all possibilities
max_prod = max(num, max_prod * num, min_prod * num)
min_prod = min(num, temp_max * num, min_prod * num)
result = max(result, max_prod)
return result
This version is clearer and doesnโt require the swap trick.
๐งฎ Step 5: Understanding as Dynamic Programming
Letโs formalize this as DP:
Define:
max_dp[i]= maximum product of subarray ending at index imin_dp[i]= minimum product of subarray ending at index i
Recurrence relation:
max_dp[i] = max(nums[i], max_dp[i-1] * nums[i], min_dp[i-1] * nums[i])
min_dp[i] = min(nums[i], max_dp[i-1] * nums[i], min_dp[i-1] * nums[i])
Base case:
max_dp[0] = nums[0]
min_dp[0] = nums[0]
Answer:
max(max_dp[0], max_dp[1], ..., max_dp[n-1])
Hereโs the explicit DP version:
class Solution:
def maxProduct(self, nums: List[int]) -> int:
n = len(nums)
max_dp = [0] * n
min_dp = [0] * n
max_dp[0] = min_dp[0] = nums[0]
result = nums[0]
for i in range(1, n):
max_dp[i] = max(nums[i],
max_dp[i-1] * nums[i],
min_dp[i-1] * nums[i])
min_dp[i] = min(nums[i],
max_dp[i-1] * nums[i],
min_dp[i-1] * nums[i])
result = max(result, max_dp[i])
return result
โฑ๏ธ Time: $O(n)$
๐ฆ Space: $O(n)$
๐ Step 6: Handling Zero
Zero is a special case โ it resets everything:
- Any product with 0 becomes 0
- It acts as a natural โrestart pointโ
Our algorithm handles this naturally:
max_prod = max(num, max_prod * num, min_prod * num)
When num = 0:
max_prod = max(0, max_prod * 0, min_prod * 0) = 0- Effectively starts fresh from the next element
๐ฏ Key Insights
-
Track both extremes: Unlike sum, we need both maximum and minimum products.
-
Negative numbers flip: A negative number can turn minimum into maximum.
-
Three choices: At each position, consider starting fresh, extending max, or extending min.
-
Zero resets: Zero naturally acts as a divider between subarrays.
-
Even negatives: An even number of negatives gives a positive product.
๐ Complexity Summary
| Approach | Time | Space |
|---|---|---|
| Brute Force | $O(n^2)$ or $O(n^3)$ | $O(1)$ |
| DP with Arrays | $O(n)$ | $O(n)$ |
| Space Optimized | $O(n)$ | $O(1)$ |
๐ Edge Cases to Consider
- All negative numbers:
[-2, -3, -4]โ Answer: 3 (single element) - Contains zero:
[2, 0, -3]โ Answer: 2 - Single element:
[5]โ Answer: 5 - Two negatives:
[-2, -3]โ Answer: 6 - Alternating signs:
[2, -5, -2, -4, 3]โ Answer: 24
๐ก Common Mistakes
- Forgetting minimum tracker: Only tracking maximum misses negative ร negative = positive
- Not handling zero: Zero should reset the product chain
- Wrong swap logic: Swapping at wrong time or not considering all three options
- Integer overflow: For very large products (though problem guarantees 32-bit fit)
๐ Comparison with Maximum Subarray
| Aspect | Maximum Sum | Maximum Product |
|---|---|---|
| Negative impact | Always decreases | Can increase (neg ร neg) |
| Trackers needed | 1 (max) | 2 (max and min) |
| Zero handling | Neutral | Resets to 0 |
| Complexity | Simpler | More complex |
๐ก Related Problems
- Maximum Subarray - The sum version
- House Robber - Similar DP pattern
- Maximum Product of Three Numbers - Related product problem
- Best Time to Buy and Sell Stock - Similar optimization pattern
๐ Why This Problem Matters
- Interview favorite: Tests understanding of DP and edge cases
- Negative number handling: Teaches how signs affect optimization
- State tracking: Shows when you need multiple states
- Real-world applications: Financial calculations, optimization problems