Maximum Product Subarray
MedPrompt
Given an integer array nums, find a subarray 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: [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.
Constraints:
1 <= nums.length <= 2 * 104-10 <= nums[i] <= 10- The product of any subarray of
numsis guaranteed to fit in a 32-bit integer.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach iterates through all possible contiguous subarrays, calculates the product of each, and keeps track of the maximum product found. It's the most straightforward but least efficient method.
Algorithm
- Initialize
max_productwith the first element ofnums. - Iterate through the array with index
ifrom 0 ton-1(this will be the start of the subarray). - Inside the first loop, initialize a
current_productto 1. - Start a nested loop with index
jfromiton-1(this will be the end of the subarray). - In the inner loop, multiply
current_productbynums[j]. - Compare
current_productwithmax_productand updatemax_productifcurrent_productis larger. - After both loops complete, return
max_product.
Walkthrough
The brute-force method systematically considers every possible subarray. We can define a subarray by its starting and ending indices.
We use two nested loops. The outer loop, with index i, selects the start of the subarray. The inner loop, with index j, selects the end of the subarray.
For each subarray nums[i...j], we calculate its product. A running product can be maintained within the inner loop to avoid a third loop for calculation. The maximum product found across all subarrays is updated and stored. The initial maximum product can be set to the first element of the array to handle cases where all numbers are negative.
class Solution { public int maxProduct(int[] nums) { if (nums.length == 0) { return 0; } int maxProduct = nums[0]; for (int i = 0; i < nums.length; i++) { int currentProduct = 1; for (int j = i; j < nums.length; j++) { currentProduct *= nums[j]; if (currentProduct > maxProduct) { maxProduct = currentProduct; } } } return maxProduct; }}Complexity
Time
O(n^2)
Space
O(1)
Trade-offs
Pros
Simple to understand and implement.
Guaranteed to find the correct solution as it checks every possibility.
Cons
Highly inefficient with a time complexity of O(n^2), making it too slow for large inputs.
Likely to cause a 'Time Limit Exceeded' error on competitive programming platforms.
This clever approach finds the maximum product in a single pass by keeping track of the product from the left and the product from the right simultaneously. It correctly handles negative numbers and zeros.
Algorithm
- Initialize
max_productwith the largest element innumsto handle edge cases. - Initialize
left_product = 1andright_product = 1. - Get the length of the array,
n. - Iterate with index
ifrom 0 ton-1: - If
left_productis 0, reset it to 1. Then, updateleft_productby multiplying withnums[i]. - If
right_productis 0, reset it to 1. Then, updateright_productby multiplying withnums[n-1-i]. - Update
max_product = max(max_product, left_product, right_product). - Return
max_product.
Walkthrough
The core idea is that the maximum product subarray will not contain a zero (unless the max product is zero itself). Zeros act as separators, breaking the problem into smaller subproblems.
For a subarray without zeros, the maximum product is either the product of all its elements (if there's an even number of negatives) or the product of a subsegment that excludes one of the endmost negative numbers (if there's an odd number of negatives).
By calculating prefix products (from left to right) and suffix products (from right to left), we are guaranteed to find these maximums. The left-to-right pass will find the maximum product for subarrays with an odd number of negatives that ends at the right boundary. The right-to-left pass handles the case where it ends at the left boundary.
We can implement this in a single loop. We maintain two variables: left_product and right_product. In each iteration i, we update left_product with nums[i] and right_product with nums[n-1-i]. If either product becomes zero, we reset it to 1 to start a new subproblem.
class Solution { public int maxProduct(int[] nums) { if (nums.length == 0) { return 0; } int maxProduct = nums[0]; int leftProduct = 1; int rightProduct = 1; int n = nums.length; for (int i = 0; i < n; i++) { // Reset if product becomes 0 leftProduct = (leftProduct == 0 ? 1 : leftProduct) * nums[i]; rightProduct = (rightProduct == 0 ? 1 : rightProduct) * nums[n - 1 - i]; maxProduct = Math.max(maxProduct, Math.max(leftProduct, rightProduct)); } return maxProduct; }}Complexity
Time
O(n)
Space
O(1)
Trade-offs
Pros
Optimal time complexity of O(n).
Constant space complexity O(1).
Relatively simple to implement once the logic is understood.
Cons
The reasoning for its correctness, especially with an odd number of negative values, can be less intuitive than the dynamic programming approach.
This approach adapts Kadane's algorithm, which is used for the maximum sum subarray problem. To handle negative numbers, we track both the maximum and minimum product of subarrays ending at the current position.
Algorithm
- If the array is empty, return 0.
- Initialize
max_so_far,min_so_far, andresultto the first elementnums[0]. - Iterate through the array from the second element (
i = 1ton-1): - Let
current_num = nums[i]. - Store the old
max_so_farin a temporary variable, saytemp_max, as it's needed for themin_so_farcalculation. - The new
max_so_faris the maximum ofcurrent_num,current_num * max_so_far, andcurrent_num * min_so_far. - The new
min_so_faris the minimum ofcurrent_num,current_num * temp_max, andcurrent_num * min_so_far. - Update
result = max(result, max_so_far). - Return
result.
Walkthrough
The standard Kadane's algorithm for maximum sum subarray doesn't work directly because multiplying by a negative number can turn a maximum product into a minimum and vice-versa. For example, a large negative product, when multiplied by another negative number, can become the new maximum positive product.
To solve this, we maintain two variables at each step i:
max_so_far: the maximum product of a subarray ending ati.min_so_far: the minimum product of a subarray ending ati.
When we process the next number num = nums[i], the new max_so_far can be one of three values: num itself (starting a new subarray), num * previous_max_so_far, or num * previous_min_so_far. The same logic applies to the new min_so_far. We also keep a global result variable that is updated with max_so_far at each step.
class Solution { public int maxProduct(int[] nums) { if (nums.length == 0) { return 0; } int maxSoFar = nums[0]; int minSoFar = nums[0]; int result = maxSoFar; for (int i = 1; i < nums.length; i++) { int curr = nums[i]; // Store the old maxSoFar because we need it to calculate the new minSoFar int tempMax = Math.max(curr, Math.max(maxSoFar * curr, minSoFar * curr)); minSoFar = Math.min(curr, Math.min(maxSoFar * curr, minSoFar * curr)); maxSoFar = tempMax; result = Math.max(maxSoFar, result); } return result; }}Complexity
Time
O(n)
Space
O(1)
Trade-offs
Pros
Optimal time complexity of O(n).
Constant space complexity O(1).
A standard and powerful dynamic programming pattern that can be adapted to similar problems.
Cons
The logic can be slightly more complex to formulate than the brute-force approach.
Solutions
Solution
public class Solution { public int MaxProduct(int[] nums) { int f = nums[0], g = nums[0], ans = nums[0]; for (int i = 1; i < nums.Length; ++i) { int ff = f, gg = g; f = Math.Max(nums[i], Math.Max(ff * nums[i], gg * nums[i])); g = Math.Min(nums[i], Math.Min(ff * nums[i], gg * nums[i])); ans = Math.Max(ans, f); } return ans; }}class Solution {public int maxProduct(int[] nums) { int f = nums[0], g = nums[0], ans = nums[0]; for (int i = 1; i < nums.length; ++i) { int ff = f, gg = g; f = Math.max(nums[i], Math.max(ff * nums[i], gg * nums[i])); g = Math.min(nums[i], Math.min(ff * nums[i], gg * nums[i])); ans = Math.max(ans, f); } return ans; }}/** * @param {number[]} nums * @return {number} */ var maxProduct = function ( nums,) { let [f, g, ans] = [nums[0], nums[0], nums[0]]; for (let i = 1; i < nums.length; ++i) { const [ff, gg] = [f, g]; f = Math.max(nums[i], ff * nums[i], gg * nums[i]); g = Math.min(nums[i], ff * nums[i], gg * nums[i]); ans = Math.max(ans, f); } return ans;};class Solution {public: int maxProduct(vector<int> &nums) { int f = nums[0], g = nums[0], ans = nums[0]; for (int i = 1; i < nums.size(); ++i) { int ff = f, gg = g; f = max({nums[i], ff * nums[i], gg * nums[i]}); g = min({nums[i], ff * nums[i], gg * nums[i]}); ans = max(ans, f); } return ans; }};class Solution: def maxProduct(self, nums: List[int]) -> int: ans = f = g = nums[0] for x in nums[1:]: ff, gg = f, g f = max(x, ff * x, gg * x) g = min(x, ff * x, gg * x) ans = max(ans, f) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.