DSAPrep
Medium1-D DP

Maximum Product Subarray

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. The product of an array with a single element is the value of that element.

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 contiguous subarray (0 sits between them).

Constraints

  • 1 <= nums.length <= 2 * 10^4
  • -10 <= nums[i] <= 10
  • The product of any subarray of nums is guaranteed to fit in a 32-bit integer.
View original on LeetCode ↗

Kadane’s algorithm for maximum sum subarray does not directly work for products, because multiplying by a negative number flips the sign – the smallest (most negative) product ending at i-1 can become the largest product ending at i if nums[i] is negative. So track two running values at each position: maxDP[i], the largest product of a subarray ending exactly at i, and minDP[i], the smallest (most negative). Both are needed because either one might flip into the other’s role on the next step.

Brute Force

Time O(n²)Space O(1)

Try every subarray directly: for each starting index, extend the ending index one step at a time, keeping a running product, and track the best seen.

class Solution:
def maxProduct(self, nums: list[int]) -> int:
n = len(nums)
best = nums[0]
for i in range(n):
prod = 1
for j in range(i, n):
prod *= nums[j]
best = max(best, prod)
return best

O(n²) subarrays, each extended in O(1) amortized work by reusing the running product → O(n²) time, O(1) space.

Bottom-Up DP (Track Max and Min)

OptimalTime O(n)Space O(n)

At each index, the best subarray ending there either starts fresh at nums[i], or extends the previous best-max subarray, or extends the previous best-min subarray (which flips sign into a new max if nums[i] is negative). Take the max and min of all three candidates.

class Solution:
def maxProduct(self, nums: list[int]) -> int:
maxDP = [0] * len(nums)
minDP = [0] * len(nums)
maxDP[0] = minDP[0] = nums[0]
ans = nums[0]
for i in range(1, len(nums)):
candidates = (nums[i], maxDP[i - 1] * nums[i], minDP[i - 1] * nums[i])
maxDP[i] = max(candidates)
minDP[i] = min(candidates)
ans = max(ans, maxDP[i])
return ans

Trace for nums = [2, 3, -2, 4] (the maxDP array is shown; minDP and the running answer are tracked as variables):

2
0
·
1
·
2
·
3
minDP = 2answer = 2
1 / 4
comparingresult

maxDP[0] = minDP[0] = nums[0] = 2. Only one element so far.

One pass, constant work per index → O(n) time. The full maxDP/minDP arrays cost O(n) space here, though only the previous entry of each is ever needed, so this can be reduced to O(1) space with two rolling variables.