Maximum Product Subarray

Problem

Given an integer array nums, return the maximum product of a contiguous subarray within nums.

  • 1 ≤ nums.length ≤ 2 * 10⁴
  • −10 ≤ nums[i] ≤ 10
  • The product of any prefix or suffix of nums fits in a 32-bit integer

Example

Input: nums = [2,3,-2,4]
Output: 6

The subarray [2,3] has the maximum product 6. A brute-force approach would check all subarrays, calculating their products, which is inefficient. The key challenge is handling negative numbers, which can flip the sign of the product and thus affect the maximum product calculation. For example, the product of two negative numbers is positive, so tracking only the maximum product at each step is insufficient. The algorithm maintains both the current maximum and minimum products to capture this duality. At each iteration, it updates these values by considering the current number, the product of the current number with the previous maximum, and the product with the previous minimum. This ensures that the algorithm correctly handles sign changes and finds the global maximum product in a single pass.

Approach

Straightforward Solution

A brute-force approach would consider every possible subarray, compute its product, and track the maximum. This approach is O(n^2) and inefficient for large inputs.

Core Observation

The maximum product subarray problem requires tracking both the maximum and minimum products ending at each position because a negative number can turn a minimum product into a maximum product and vice versa.

Path to Optimal

The key recognition signals are 'maximum product' and 'contiguous subarray' with the presence of negative numbers. These indicate a Dynamic Programming approach that tracks state at each position. The insight is to maintain two running values: the maximum and minimum products ending at the current index. This dual tracking handles sign flips caused by negative numbers. At each step, the algorithm updates these values by considering the current number alone, the product with the previous maximum, and the product with the previous minimum. This approach reduces the problem to a single pass with O(n) time complexity.

Optimal Approach

Use a single pass through the array, maintaining two variables: cur_max and cur_min, representing the maximum and minimum products ending at the current index. For each number, compute the possible candidates (current number, current number times cur_max, current number times cur_min), update cur_max and cur_min accordingly, and track the global maximum product found so far. This approach correctly handles negative numbers and zeros, ensuring the maximum product subarray is found efficiently.

Time

O(n)

The algorithm iterates through the array once, performing constant time operations at each step.

Space

O(1)

Only a fixed number of variables are used to track the current maximum, minimum, and result, regardless of input size.

Pattern Spotlight

Dynamic Programming (Tracking Dual States)

When the problem involves products with negative numbers, maintain both the maximum and minimum products at each step because a negative number can flip the sign, turning a minimum into a maximum and vice versa.

Solution

1class Solution:
2 def maxProduct(self, nums: list[int]) -> int:
3 if not nums:
4 return 0
5
6 res = nums[0]
7 cur_min, cur_max = 1, 1
8
9 for n in nums:
10 temp_max_product = cur_max * n
11
12 cur_max = max(n, temp_max_product, n * cur_min)
13 cur_min = min(n, temp_max_product, n * cur_min)
14
15 res = max(res, cur_max)
16
17 return res

Step-by-Step Solution

1

Handle Empty Input Edge Case to Avoid Errors

3if not nums:
4 return 0

Objective

To immediately return 0 if the input array is empty, preventing errors in subsequent logic.

Key Insight

An empty input means no subarray exists, so the maximum product is trivially 0. Handling this edge case upfront avoids unnecessary computation and potential runtime errors.

Interview Quick-Check

State & Boundaries

Check for empty input to handle edge cases where no subarray exists.

2

Initialize Tracking Variables for Maximum Product Calculation

6res = nums[0]
7cur_min, cur_max = 1, 1

Objective

To set up variables that track the global maximum product and the current maximum and minimum products ending at each position.

Key Insight

Initializing res to the first element ensures the result starts with a valid subarray product. Setting cur_max and cur_min to 1 prepares for multiplication without affecting the first iteration. These variables enable the algorithm to track the best and worst products dynamically as it iterates through the array.

Interview Quick-Check

Core Logic

Initialize res with the first element to handle arrays with a single element correctly.

Common Pitfalls & Bugs

Starting cur_max and cur_min at 1 avoids incorrect multiplication results in the first iteration.

3

Iterate Through the Array Updating Maximum and Minimum Products

9for n in nums:
10 temp_max_product = cur_max * n
12 cur_max = max(n, temp_max_product, n * cur_min)
13 cur_min = min(n, temp_max_product, n * cur_min)
15 res = max(res, cur_max)

Objective

To update the current maximum and minimum products at each step by considering the current number and its products with previous max and min, and update the global maximum accordingly.

Key Insight

At each iteration, the algorithm considers three candidates for both the current maximum and minimum products: the current number alone, the product of the current number with the previous maximum, and the product with the previous minimum. This accounts for the possibility that a negative number flips the sign, turning a minimum into a maximum and vice versa. Updating res with the current maximum ensures the global maximum product is tracked throughout the iteration.

Interview Quick-Check

Core Logic

Maintain both cur_max and cur_min to handle sign changes caused by negative numbers, updating them by considering the current number and its products with previous cur_max and cur_min.

State & Boundaries

Update res with the maximum of itself and cur_max to track the global maximum product.

Common Pitfalls & Bugs

Failing to consider the product with cur_min can miss cases where two negatives produce a larger positive product.

4

Return the Global Maximum Product Found

17return res

Objective

To return the maximum product of any contiguous subarray found during iteration.

Key Insight

After processing all elements, res holds the maximum product subarray value. Returning it completes the algorithm and provides the correct answer.

Interview Quick-Check

Core Logic

Return the tracked global maximum product after processing all elements.

Line Analysis

This solution has 2 Critical lines interviewers watch for.

Line 12 Critical
cur_max = max(n, temp_max_product, n * cur_min)

Update the current maximum product by taking the maximum of the current number, the product of the current number and previous maximum, and the product of the current number and previous minimum.

This line is the heart of the algorithm; it simultaneously considers the current number and its products with previous max and min to correctly handle sign changes and identify the maximum product subarray ending at the current index.

Line 13 Critical
cur_min = min(n, temp_max_product, n * cur_min)

Update the current minimum product by taking the minimum of the current number, the product of the current number and previous maximum, and the product of the current number and previous minimum.

This line complements the max update by tracking the minimum product, enabling the algorithm to capture potential sign flips that can turn a small negative product into a large positive product.

Line 6 Core
res = nums[0]

Initialize the result with the first element of the array.

Starting res with nums[0] ensures the algorithm correctly handles arrays with a single element and sets a valid baseline for comparison.

Line 15 Core
res = max(res, cur_max)

Update the global maximum product with the current maximum product.

This ensures the algorithm keeps track of the best product found so far across all subarrays.

Line 3 Core
if not nums:

Check if the input array is empty.

This early check prevents errors and unnecessary computation by handling the edge case where no subarray exists.

Line 7 Core
cur_min, cur_max = 1, 1

Initialize current minimum and maximum products to 1.

Setting cur_min and cur_max to 1 prepares for multiplication without affecting the first iteration, enabling correct tracking of products.

Line 10 Core
temp_max_product = cur_max * n

Calculate the product of the current maximum product and the current number.

Storing this intermediate value allows the algorithm to compare multiple candidates for the new maximum and minimum products without losing the original cur_max value.

Line 17 Core
return res

Return the maximum product found.

Returning res provides the final answer after processing all elements, completing the algorithm.

Line 4 Standard
return 0

Return 0 for empty input.

Returning 0 is the correct response when no elements exist to form a subarray product.

Line 9 Standard
for n in nums:

Iterate through each number in the array.

This loop processes every element to update the running maximum and minimum products, which is essential for finding the maximum product subarray.

Test Your Understanding

Why must the algorithm track both the current maximum and minimum products at each step?

Because a negative number can turn the minimum product into a maximum product and vice versa, tracking both ensures that the algorithm correctly captures the effect of sign changes on the product.

Related Problems

Dynamic Programming pattern

Don't just read it. Drill it.

Reconstruct Maximum Product Subarray from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Maximum Product Subarray drill

or drill a free problem