Problem overview
MaxProduct receives an int[] nums and returns the largest product of a subarray: one or more elements that stand next to each other, multiplied together. The product of any subarray is guaranteed to fit in an int.
For [2,3,-2,4] the answer is 6, from [2,3]; the only negative number, -2, makes every subarray that includes it negative. For [-2,0,-1] the answer is 0. It is not 2, because [-2,-1] is not a subarray: the 0 stands between them. For [-2,3,-4] the answer is 24: the two negatives cancel out over the whole array.
Approach: keep the largest and the smallest product ending here
For a sum, you would keep only the best subarray ending at each position. A product is trickier, because a negative number turns the most negative product into the most positive one. So the smallest product ending at the previous element can become the largest one at this element. That is why two values are kept for the current position:
maxHere: the largest product of a subarray ending at the current element;minHere: the smallest product of a subarray ending at the current element.
At each new element x, the best subarray ending at x either starts fresh at x or extends the best one ending just before it. Starting fresh is also how a product recovers after a 0, since multiplying by 0 destroys everything before it. If x is negative, swap maxHere and minHere first, because the multiplication flips their order. A third variable, result, remembers the largest maxHere seen. One pass with constant work per element gives O(n) time and O(1) extra space.
Step by step
maxHere = nums[0]. At the first element, the only subarray ending there is the element itself.minHere = nums[0]. The smallest product is kept too, since the next negative number may turn it into the largest.result = nums[0]. A subarray needs at least one element, so the first element alone is a valid start, even if it is negative.- Loop
ifrom 1 whilei < nums.Length. Element 0 is already counted. - Negative element: if
nums[i] < 0, swapmaxHereandminHerewith a tuple assignment. After the swap, each variable is the right one to multiply bynums[i]. - Update the maximum:
maxHere = Math.Max(nums[i], maxHere * nums[i]). - Update the minimum:
minHere = Math.Min(nums[i], minHere * nums[i]). This line reads onlyminHereandnums[i], so it is not affected by the newmaxHerecomputed just before. - Update the answer:
result = Math.Max(result, maxHere). The best subarray ends at some element, so checking the best one ending at each element finds it. - Return
result.
The solution in C#
class Solution {
public int MaxProduct(int[] nums) {
int maxHere = nums[0];
int minHere = nums[0];
int result = nums[0];
for (int 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;
}
}
C# details worth knowing:
- Tuple swap.
(maxHere, minHere) = (minHere, maxHere);builds a pair from the right-hand values and then assigns its parts to the left-hand variables. The right side is evaluated completely first, so no temporary variable is needed. This is a feature of C# 7 and later. Math.MaxandMath.Mintake two numbers of the same type and return one. They live inSystem, and C# method names are PascalCase.intoverflow wraps silently in the default unchecked context. The lesson guarantees that every subarray product fits in anint, butmaxHereandminHereare also products of subarrays, so they fit too. For arrays without that guarantee,longwould be the safer type. A constant expression such asint.MaxValue * 2is a compile error (CS0220), but a product of variables, such as100000 * 100000held in twointvariables, simply wraps, giving 1410065408.nums[0]needs a non-empty array. An empty array would throwIndexOutOfRangeException. The lesson guarantees at least one element.
Complexity
Time: O(n). The loop visits each element once, and each visit does a few multiplications and comparisons.
Space: O(1). Three int variables, whatever the length of nums.
Edge cases and common mistakes
- A single negative number.
[-3]returns -3. Startingresultat 0 would wrongly return 0. - Zeros split the array. For
[-2,3,-4,0,5]the answer is 24, from the part before the 0. Because each step can start fresh atnums[i], the run after the 0 starts clean, and[0,2]returns 2. - Two negatives. A negative
maxHereturns into the answer when another negative arrives: at[-2,3,-4]the minimum -6 times -4 gives 24. - Keeping only
maxHere. This fails on[-2,3,-4], where the best product comes from the most negative one before the second negative number. - Forgetting the swap, or doing it after the multiplications. The variables would then multiply by the wrong partner, and the maximum and minimum would be mixed up when
nums[i]is negative. - Starting the loop at 0. Element 0 would be multiplied into itself, squaring it.
Testing
Printing the three values after each step shows how the swap works. For [2,3,-2,4]:
start max=2 min=2
i=1 x=3 max=6 min=3 result=6
i=2 x=-2 max=-2 min=-12 result=6
i=3 x=4 max=4 min=-48 result=6
For [-2,3,-4]:
start max=-2 min=-2
i=1 x=3 max=3 min=-6 result=3
i=2 x=-4 max=24 min=-12 result=24
Other inputs and the results of MaxProduct:
nums | Result |
|---|---|
[-2,0,-1] | 0 |
[-3] | -3 |
[0,2] | 2 |
[-2,3,-4,0,5] | 24 |
