Language Selection
Choose your preferred programming language
Maximum Product Subarray
Problem Statement
Given an integer array nums, pick a non-empty contiguous block of elements (a subarray) whose product is as large as possible, and return that product.
The input is chosen so that the product of every subarray fits in a signed 32-bit integer.
Examples
Example 1:
Input: nums = [3,-1,4,2]
Output: 8
Explanation: -1 is the only negative value, so any subarray that includes it has a negative
product. The best subarray without it is [4,2], with product 8.
Example 2:
Input: nums = [-3,0,-5]
Output: 0
Explanation: Every non-empty subarray is either a single negative number or contains 0,
so the best product is 0 (the subarray [0]).
Example 3:
Input: nums = [-2,5,-3,1]
Output: 30
Explanation: Two negatives cancel out: [-2,5,-3] has product 30, and multiplying by 1 keeps it at 30.
Constraints
1 <= nums.length <= 2 * 10^4-10 <= nums[i] <= 10- The product of any subarray of
numsfits in a 32-bit signed integer.
Approach 1: Brute Force over All Subarrays
Algorithm
Fix a start index i, then extend the subarray one element at a time to the right, keeping a running product. Every extension is a new subarray, so compare its product with the best answer seen so far. Reusing the running product avoids recomputing each subarray from scratch.
- Set
best = nums[0](a valid single-element subarray). - For every start
i:- Set
product = 1. - For every end
j >= i, multiplyproductbynums[j]and updatebest = max(best, product).
- Set
- Return
best.
Implementation
Python:
from typing import List
class Solution:
def maxProduct(self, nums: List[int]) -> int:
best = nums[0]
n = len(nums)
for i in range(n):
product = 1
for j in range(i, n):
product *= nums[j] # product of nums[i..j]
best = max(best, product)
return best
Java:
class Solution {
public int maxProduct(int[] nums) {
int best = nums[0];
int n = nums.length;
for (int i = 0; i < n; i++) {
int product = 1;
for (int j = i; j < n; j++) {
product *= nums[j]; // product of nums[i..j]
best = Math.max(best, product);
}
}
return best;
}
}
Go:
func maxProduct(nums []int) int {
best := nums[0]
n := len(nums)
for i := 0; i < n; i++ {
product := 1
for j := i; j < n; j++ {
product *= nums[j] // product of nums[i..j]
if product > best {
best = product
}
}
}
return best
}
JavaScript:
function maxProduct(nums) {
let best = nums[0];
const n = nums.length;
for (let i = 0; i < n; i++) {
let product = 1;
for (let j = i; j < n; j++) {
product *= nums[j]; // product of nums[i..j]
best = Math.max(best, product);
}
}
return best;
}
C#:
using System;
public class Solution {
public int MaxProduct(int[] nums) {
int best = nums[0];
int n = nums.Length;
for (int i = 0; i < n; i++) {
int product = 1;
for (int j = i; j < n; j++) {
product *= nums[j]; // product of nums[i..j]
best = Math.Max(best, product);
}
}
return best;
}
}
C++:
#include <algorithm>
#include <vector>
using namespace std;
class Solution {
public:
int maxProduct(vector<int>& nums) {
int best = nums[0];
int n = nums.size();
for (int i = 0; i < n; i++) {
int product = 1;
for (int j = i; j < n; j++) {
product *= nums[j]; // product of nums[i..j]
best = max(best, product);
}
}
return best;
}
};
Complexity Analysis
- Time: O(n²): there are n(n+1)/2 subarrays and each is handled in O(1) thanks to the running product.
- Space: O(1).
Approach 2: Track Running Max and Min (Optimal)
Algorithm
This is Kadane’s algorithm adapted to products. For sums, the best subarray ending at index i depends only on the best one ending at i - 1. For products that is not enough: a very negative product can become the largest product after multiplying by a negative number. So for each index we track two values:
curMax: the largest product of a subarray that ends aticurMin: the smallest (most negative) product of a subarray that ends ati
A subarray ending at i either starts fresh at nums[i] or extends the best/worst subarray ending at i - 1. When nums[i] is negative, multiplying flips the order, so the old minimum produces the new maximum and vice versa. Swapping curMax and curMin before the update handles that. A zero resets both to 0, and the “start fresh” option lets the next element begin a new subarray.
- Set
curMax = curMin = best = nums[0]. - For each
ifrom 1 ton - 1, withx = nums[i]:- If
x < 0, swapcurMaxandcurMin. curMax = max(x, curMax * x)curMin = min(x, curMin * x)best = max(best, curMax)
- If
- Return
best.
Every value that is ever computed (curMax * x, curMin * x) is the product of a real subarray, so the 32-bit guarantee covers all intermediate values.
Implementation
Python:
from typing import List
class Solution:
def maxProduct(self, nums: List[int]) -> int:
cur_max = cur_min = best = nums[0]
for i in range(1, len(nums)):
x = nums[i]
if x < 0:
# A negative factor turns the smallest product into the largest
cur_max, cur_min = cur_min, cur_max
cur_max = max(x, cur_max * x) # start fresh or extend
cur_min = min(x, cur_min * x)
best = max(best, cur_max)
return best
Java:
class Solution {
public int maxProduct(int[] nums) {
int curMax = nums[0], curMin = nums[0], best = nums[0];
for (int i = 1; i < nums.length; i++) {
int x = nums[i];
if (x < 0) {
// A negative factor turns the smallest product into the largest
int tmp = curMax;
curMax = curMin;
curMin = tmp;
}
curMax = Math.max(x, curMax * x); // start fresh or extend
curMin = Math.min(x, curMin * x);
best = Math.max(best, curMax);
}
return best;
}
}
Go:
func maxProduct(nums []int) int {
curMax, curMin, best := nums[0], nums[0], nums[0]
for _, x := range nums[1:] {
if x < 0 {
// A negative factor turns the smallest product into the largest
curMax, curMin = curMin, curMax
}
curMax = max(x, curMax*x) // start fresh or extend (built-in max, Go 1.21+)
curMin = min(x, curMin*x)
best = max(best, curMax)
}
return best
}
JavaScript:
function maxProduct(nums) {
let curMax = nums[0], curMin = nums[0], best = nums[0];
for (let i = 1; i < nums.length; i++) {
const x = nums[i];
if (x < 0) {
// A negative factor turns the smallest product into the largest
[curMax, curMin] = [curMin, curMax];
}
curMax = Math.max(x, curMax * x); // start fresh or extend
curMin = Math.min(x, curMin * x);
best = Math.max(best, curMax);
}
return best;
}
C#:
using System;
public class Solution {
public int MaxProduct(int[] nums) {
int curMax = nums[0], curMin = nums[0], best = nums[0];
for (int i = 1; i < nums.Length; i++) {
int x = nums[i];
if (x < 0) {
// A negative factor turns the smallest product into the largest
(curMax, curMin) = (curMin, curMax);
}
curMax = Math.Max(x, curMax * x); // start fresh or extend
curMin = Math.Min(x, curMin * x);
best = Math.Max(best, curMax);
}
return best;
}
}
C++:
#include <algorithm>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int maxProduct(vector<int>& nums) {
int curMax = nums[0], curMin = nums[0], best = nums[0];
for (size_t i = 1; i < nums.size(); i++) {
int x = nums[i];
if (x < 0) {
// A negative factor turns the smallest product into the largest
swap(curMax, curMin);
}
curMax = max(x, curMax * x); // start fresh or extend
curMin = min(x, curMin * x);
best = max(best, curMax);
}
return best;
}
};
Complexity Analysis
- Time: O(n): one pass with constant work per element.
- Space: O(1): three variables, regardless of input size.
Key Insights
- Negatives flip the order. The smallest product ending at
i - 1becomes the largest after multiplying by a negative number, so the minimum is just as important as the maximum. - Zero splits the array. A zero forces both running values to 0; the
max(x, ...)/min(x, ...)“start fresh” option lets the next element begin a new subarray. - Kadane with two states. It is the same “best ending here” dynamic programming idea as Maximum Subarray, with a second state for the minimum.
- Prefix/suffix view. Between zeros, the best product is always a prefix or a suffix of that segment (an even number of negatives, or dropping everything up to the first or after the last negative). Scanning running products from both ends gives another O(n) solution.
Edge Cases
- Single negative element:
[-7]returns-7; the answer must be a non-empty subarray, so do not default to 0. - All zeros and negatives:
[-3,0,-5]returns0. - Even number of negatives:
[-2,-3,-4]returns12from[-3,-4], while[-2,-3]gives only 6. - Zeros in the middle:
[2,3,0,4,5]returns20; the zero splits the array into two independent pieces. - Large products:
[10,10,10,10,10,10,10,10,10]returns1000000000, close to the 32-bit limit but inside it.
Common Mistakes
- Tracking only the maximum. Plain Kadane’s on products fails on
[-2,5,-3], where the answer 30 comes from the earlier minimum-10times-3. - Updating
curMaxbefore computingcurMinwithout a swap or temp. WritingcurMax = max(x, curMax*x, curMin*x)and thencurMin = min(x, curMax*x, curMin*x)uses the already-overwrittencurMax. Save the old value or swap first. - Initialising
bestto 0. That returns 0 for[-7]. Start fromnums[0]. - Resetting to 1 on zero and forgetting the zero itself. If you reset after a zero, still count
0as a candidate answer, or[-1,0,-2]returns the wrong value. - Ignoring overflow assumptions. The problem guarantees every subarray product fits in 32 bits; without that guarantee you would need 64-bit or big-integer arithmetic.
Interview Tips
- Start from Maximum Subarray. Say “this looks like Kadane’s”, then explain why one running value is not enough for products. That shows you can adapt a known pattern instead of memorising it.
- Walk through
[-2,5,-3]out loud. It shows the minimum turning into the maximum in three steps. - Explain zeros explicitly. Interviewers often probe what happens at a zero; the “start fresh” term answers it.
- Offer the prefix/suffix trick as an alternative if asked for another O(n) method.
Follow-up Questions
- Return the subarray itself, not only the product: record the start index whenever a “start fresh” choice wins, and the end index whenever
bestimproves. - No overflow guarantee: use 64-bit integers, big integers, or compare logarithms of absolute values while tracking the sign.
- Maximum sum subarray (LC 53): the same pattern with a single running value.
- Subarray product less than k (LC 713): with positive numbers only, a sliding window counts subarrays in O(n).
- Circular array: for sums you combine the max subarray with total minus the min subarray; ask how that idea changes for products.