EasyGreedy

Maximum Product of Three Numbers โ€” Solution

Problem

Given an integer array, find the largest possible product formed by multiplying exactly three of its elements. Negative numbers are the complication โ€” two deeply negative values multiply to a large positive, which can beat any combination of positives.

  • Input: nums = [-4, -3, 5, 2, 1]
  • Output: 60
  • Explanation: (-4) ร— (-3) ร— 5 = 60, which beats the product of any three non-negative numbers in the array.

Counter-example showing why "just take the top three" fails:

  • Input: nums = [-10, -9, 1]
  • Output: 90
  • Explanation: The three largest are -9, 1, and... wait, there are only three elements. But with [-10, -9, 1, 2], the top three (1 ร— 2 ร— (-9) = -18) lose to the pair-of-negatives candidate: (-10) ร— (-9) ร— 2 = 180.

Intuition

There are only two candidates worth comparing: the product of the three largest values, and the product of the two smallest (most negative) values paired with the single largest value. Any other triple either uses only one negative (sign goes negative) or none of the extremes and can't win. Recognize these two candidates and the problem collapses to a single max call.

Approach 1 โ€” Sort

Sort the array, then read off the two candidate products from the ends.

  1. Sort nums in ascending order.
  2. Compute top_three as the product of the last three elements (nums[-1] * nums[-2] * nums[-3]).
  3. Compute neg_pair_and_max as the product of the first two elements and the last element (nums[0] * nums[1] * nums[-1]).
  4. Return the larger of the two candidates.
1def maximumProduct(self, nums: List[int]) -> int:
2    nums.sort()
3    n = len(nums)
4    # two candidates: three largest, or two most-negative ร— the global maximum
5    return max(
6        nums[n-1] * nums[n-2] * nums[n-3],
7        nums[0] * nums[1] * nums[n-1]
8    )

Time: O(n log n) โ€” dominated by sorting.
Space: O(1) โ€” sorting is in-place (O(log n) call stack internally in most implementations).

Approach 2 โ€” Linear Scan

Track the five values we care about โ€” the two smallest and three largest โ€” in a single pass, avoiding the sort entirely.

  1. Initialize min1 = min2 = +โˆž and max1 = max2 = max3 = -โˆž.
  2. For each number, check if it displaces min1; if so, push the old min1 into min2. Otherwise check if it beats min2 alone.
  3. Apply the same cascading logic downward through max1 โ†’ max2 โ†’ max3.
  4. Return the larger of max1 ร— max2 ร— max3 and min1 ร— min2 ร— max1.
1def maximumProduct(self, nums: List[int]) -> int:
2    min1 = min2 = float('inf')
3    max1 = max2 = max3 = float('-inf')
4    for num in nums:
5        # shift old min1 into the min2 slot before overwriting min1
6        if num <= min1:
7            min2, min1 = min1, num
8        elif num < min2:
9            min2 = num
10        # cascade the new champion down through the top-three slots
11        if num >= max1:
12            max3, max2, max1 = max2, max1, num
13        elif num >= max2:
14            max3, max2 = max2, num
15        elif num > max3:
16            max3 = num
17    return max(max1 * max2 * max3, min1 * min2 * max1)

Time: O(n) โ€” single pass through the array.
Space: O(1) โ€” five scalar variables regardless of input size.

Complexity Summary

ApproachTimeSpaceWhen to use
SortO(n log n)O(1)Default choice โ€” clearest to read and reason about
Linear ScanO(n)O(1)When strict O(n) is required, or as a follow-up optimization in an interview

Common Mistakes

  • Only checking the three largest: Returning nums[-1] * nums[-2] * nums[-3] (after sorting) misses the case where two deeply negative numbers pair with the maximum to form a larger product. Always evaluate both candidates.
  • Wrong cascade order in the linear scan: Writing max1 = num; max2 = max1 clobbers max1 before it can be copied into max2. The displacement must go in reverse order โ€” max3 = max2; max2 = max1; max1 = num โ€” so each slot is saved before it gets overwritten.
  • Pairing the two smallest with the second-largest: Some implementations try nums[0] * nums[1] * nums[-2] as a third candidate, but the optimal negative pair always wants the single global maximum (nums[-1]), not the second largest โ€” the global max only amplifies the pair's product further.
  • Off-by-one when porting Python's negative indexing to Java/C++: nums[-1] in Python equals nums[n-1] in Java/C++. Mixing conventions (e.g., writing nums[n] by accident) causes an out-of-bounds exception rather than a wrong answer, so it surfaces immediately โ€” but it's a common slip during contests.
  • Assuming the answer is always positive: With input [-3, -2, -1], the maximum product is (-3) ร— (-2) ร— (-1) = -6 โ€” the least negative option. Code that guards return 0 if result < 0 will silently return the wrong answer for all-negative arrays.

Related Problems

  • 3Sum โ€” find all triples summing to zero; same challenge of selecting three-element combinations while managing sign interactions
  • Maximum Product Subarray โ€” product optimization over contiguous runs, where the same two-negatives-make-a-positive insight drives the DP transitions
  • Kth Largest Element in an Array โ€” efficiently isolating the top-k values from an unsorted array without a full sort
  • Product of Array Except Self โ€” creative product manipulation that avoids recomputing shared factors across elements
  • Largest Number โ€” custom comparison-based sorting to find the lexicographically optimal arrangement of array elements

Ready to practice? Try it on SkillFlow

Adaptive problems, AI follow-up interviews, and a skill score that shows exactly where you need to improve.

Practice This Problem โ†’