Problem overview
maxProduct(nums) receives an array of integers and returns the largest product of a subarray: a run of one or more elements that stand next to each other. The product of a subarray is all its elements multiplied together.
For [2, 3, -2, 4] the answer is 6, from [2, 3]: the only negative number, -2, makes every subarray that contains it negative. For [-2, 0, -1] the answer is 0. Taking [-2, -1] would give 2, but those two aren't next to each other, because the 0 stands between them. For [-2, 3, -4] the answer is 24, from the whole array, since the two negatives cancel.
Approach: track the largest and the smallest product ending here
For a sum, the best subarray ending at a position either extends the best one ending before it or starts fresh. A product has the same two choices, but with a twist: a negative number turns the smallest (most negative) product into the largest. So one running value isn't enough. At each position the solution keeps both the largest and the smallest product of a subarray that ends there: maxHere and minHere.
Moving to the next element x:
- if
xis negative, the roles swap first, because multiplying byxturns the old smallest into the new largest and the other way round; - the new largest is the better of
xalone andmaxHere * x; - the new smallest is the lower of
xalone andminHere * x.
Starting fresh with x alone is also how a product gets past a 0: after a 0, both running values are 0, and the next element beats the product with 0 on its own. A separate variable, result, remembers the best maxHere seen at any position, since the best subarray ends somewhere.
On [2, 3, -2, 4] the values after each element, as maxHere, minHere, result, are: start 2, 2, 2; after 3: 6, 3, 6; after -2: -2, -12, 6; after 4: 4, -48, 6. The answer is 6. On [-2, 3, -4] they are: start -2, -2, -2; after 3: 3, -6, 3; after -4: 24, -12, 24. The answer is 24.
Step by step
maxHereis aletvariable starting atnums[0]. At the first element the only subarray ending there is the element itself.minHerealso starts atnums[0].resultstarts atnums[0]too. A subarray needs at least one element, so the first element alone is a valid start even when it is negative.- Loop with
let i = 1whilei < nums.length. Element 0 is already counted. - Negative element: if
nums[i] < 0, swap the two with[maxHere, minHere] = [minHere, maxHere]. - Update the largest:
maxHere = Math.max(nums[i], maxHere * nums[i]). - Update the smallest:
minHere = Math.min(nums[i], minHere * nums[i]). - Update the best:
result = Math.max(result, maxHere). - Return
result.
The solution in JavaScript
class Solution {
maxProduct(nums) {
let maxHere = nums[0];
let minHere = nums[0];
let result = nums[0];
for (let i = 1; i < nums.length; i++) {
if (nums[i] < 0) {
[maxHere, minHere] = [minHere, maxHere];
}
maxHere = Math.max(nums[i], maxHere * nums[i]);
minHere = Math.min(nums[i], minHere * nums[i]);
result = Math.max(result, maxHere);
}
return result;
}
}
Details worth knowing:
- Swapping with destructuring.
[maxHere, minHere] = [minHere, maxHere]builds an array of the two current values and unpacks it back into the variables in the other order, with no temporary variable. Both variables are declared withletabove, so assigning to them is allowed. - The two updates don't interfere. The new
maxHereis computed from the swapped values, andminHereuses its own old value, notmaxHere, so the two lines don't interfere. - A line that starts with
[continues the previous line when that line has no semicolon. The reference code ends the statement before it with one, so the swap line stands alone. - Numbers are doubles. The lesson guarantees every subarray product fits in 32 bits, far below the point where a JavaScript number loses precision, so the products are exact.
Complexity
Time: O(n). One pass, with a few comparisons and multiplications per element.
Space: O(1). Three numbers, whatever the length of the array.
Edge cases and common mistakes
- One negative element.
[-3]gives -3. That is whyresultstarts atnums[0]and not at 0. - A zero.
[0]gives 0, and[-2, 0]gives 0. A product through a 0 is 0, and the next element starts a new run. - Two negatives.
[-1, -1]gives 1. The first -1 is the smallest product so far, and multiplying it by the second -1 makes it the largest. - A negative in the middle of a long run.
[2, -5, -2, -4, 3]gives 24, from[-2, -4, 3]. Keeping only the largest product would lose the -5 that a later negative could have used. - Keeping only
maxHere.[-2, 3, -4]would then give 3 instead of 24. - Starting
resultat 0. An array like[-3]would wrongly return 0. - Swapping after the multiplication. The swap must come before the two updates, or each product uses the wrong running value.
Testing
[2, 3, -2, 4]gives 6.[-2, 0, -1]gives 0.[-2, 3, -4]gives 24.[-3]gives -3.[0, -1]gives 0.[-1, -1]gives 1.[2, -5, -2, -4, 3]gives 24.
