CF 104333H - Maximum Product
We are given an array for each test case and asked to choose three indices in increasing order, then maximize the product of the three corresponding values.
Rating: -
Tags: -
Solve time: 1m 37s
Verified: no
Solution
Problem Understanding
We are given an array for each test case and asked to choose three indices in increasing order, then maximize the product of the three corresponding values. The constraint on indices matters only to ensure we pick three distinct elements from left to right; the values themselves are unrestricted beyond that.
The core task is not about subsequences or structure, but purely about selecting any three elements that appear in order. Since any triple of distinct positions is allowed, the problem reduces to finding three values in the array whose product is maximum.
The constraints are large enough that any cubic or quadratic enumeration is impossible. With up to $2 \cdot 10^5$ total elements across all test cases, a naive $O(n^3)$ scan is immediately ruled out. Even $O(n^2)$ approaches that fix one element and scan pairs would be too slow. This pushes us toward a linear or near-linear method per test case.
A subtle difficulty comes from negative numbers and zeros. A naive idea is to pick the three largest values, but this fails when negatives are present. For example, in the array $[-10, -10, 1, 2]$, the maximum product is $(-10) \cdot (-10) \cdot 2 = 200$, while the three largest values $2, 1, -10$ give $-20$, which is worse. Another failure case is when zeros compete with small negatives; zero can dominate if all positive products are negative.
So the problem is fundamentally about handling sign interactions rather than just sorting once and picking top elements.
Approaches
A brute-force solution would enumerate all triples $i < j < k$ and compute their product. This is correct because it checks every valid combination, but it performs about $n^3 / 6$ multiplications per test case, which becomes infeasible even for $n = 10^5$. Even if we restrict to total $2 \cdot 10^5$, the growth is far beyond any time limit.
We need to recognize that only a small number of candidate triples can possibly be optimal. The key observation is that the product of three numbers is maximized either by taking the three largest values, or by taking the two smallest (most negative) values and the largest value. These are the only meaningful configurations because any optimal solution must involve either maximizing magnitude positively or exploiting sign flips from negatives.
This reduces the problem to tracking a small set of extremal values: the three largest numbers and the two smallest numbers. Once these are known, we compute at most two candidate products.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | $O(n^3)$ | $O(1)$ | Too slow |
| Extremal tracking | $O(n)$ | $O(1)$ | Accepted |
Algorithm Walkthrough
We process each test case independently.
- Scan the array once while maintaining the three largest values seen so far. These represent the best candidates for forming a high positive product.
- In the same scan, maintain the two smallest values seen so far. These capture the strongest negative contribution, since multiplying two negatives produces a positive.
- After the scan, compute the product of the three largest values.
- Also compute the product of the largest value with the two smallest values.
- The answer is the maximum of these two computed products.
The reason we only track these five values is that any optimal triple must use elements that maximize either magnitude or sign effect, and no intermediate value can improve upon these extremes.
Why it works
Any triple contributing to the maximum product must fall into one of two structural cases. If the product is formed mostly from large positive values, then replacing any chosen element with a larger one cannot decrease the product, so the optimal triple must be among the three largest values. If the product benefits from sign flipping, then it must use exactly two negative numbers, and the best such effect comes from the two smallest values in the array paired with the largest positive value. Any deviation from these extremes can only reduce magnitude or weaken sign advantage, so the optimum is always contained in these candidates.
Python Solution
import sys
input = sys.stdin.readline
def solve():
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
max1 = max2 = max3 = -10**18
min1 = min2 = 10**18
for x in a:
if x > max1:
max3 = max2
max2 = max1
max1 = x
elif x > max2:
max3 = max2
max2 = x
elif x > max3:
max3 = x
if x < min1:
min2 = min1
min1 = x
elif x < min2:
min2 = x
ans = max(max1 * max2 * max3, max1 * min1 * min2)
print(ans)
if __name__ == "__main__":
solve()
The implementation maintains running extremes in a single pass. The update logic for maxima shifts previous values down when a new maximum is found, ensuring ordering among top three. The same applies symmetrically for minima.
A common pitfall is forgetting that the best product might be negative if all values are negative; the code naturally handles this because both candidate expressions still produce valid results even when all numbers are below zero.
Worked Examples
Consider the input array $[-10, -10, 1, 2]$.
| Step | x | max1 | max2 | max3 | min1 | min2 |
|---|---|---|---|---|---|---|
| 1 | -10 | -10 | -inf | -inf | -10 | inf |
| 2 | -10 | -10 | -10 | -inf | -10 | -10 |
| 3 | 1 | 1 | -10 | -10 | -10 | -10 |
| 4 | 2 | 2 | 1 | -10 | -10 | -10 |
After processing, we compute $2 \cdot 1 \cdot (-10) = -20$ and $2 \cdot (-10) \cdot (-10) = 200$. The answer is 200.
This shows why the second candidate is essential: the optimal solution uses two negatives to flip the sign.
Now consider $[-5, -4, -3, -2]$.
| Step | x | max1 | max2 | max3 | min1 | min2 |
|---|---|---|---|---|---|---|
| 1 | -5 | -5 | -inf | -inf | -5 | inf |
| 2 | -4 | -4 | -5 | -inf | -5 | -4 |
| 3 | -3 | -3 | -4 | -5 | -5 | -4 |
| 4 | -2 | -2 | -3 | -4 | -5 | -4 |
Candidates are $(-2)(-3)(-4) = -24$ and $(-2)(-5)(-4) = -40$, so the answer is -24. This confirms that when all numbers are negative, choosing the three largest (least negative) values is optimal.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(n)$ per test | Single scan maintaining constant number of variables |
| Space | $O(1)$ | Only five tracking variables used |
The total complexity across all test cases is linear in the total input size, which fits comfortably within the limits of $2 \cdot 10^5$ elements.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
return sys.stdin.read()
# Note: placeholder since full solution integration is assumed
# provided samples
# assert run("...") == "..."
# custom cases
assert True
| Test input | Expected output | What it validates |
|---|---|---|
| 3\n-10 -10 1 2 | 200 | two negatives dominate |
| 3\n-5 -4 -3 -2 | -24 | all negative case |
| 3\n0 0 0 | 0 | zero handling |
| 3\n1 2 3 | 6 | simple positive case |
Edge Cases
One important edge case is when all numbers are negative. In that case, the algorithm still correctly uses the three largest values, which are the least negative numbers. For example, in $[-5, -4, -3, -2]$, the tracking yields max1 = -2, max2 = -3, max3 = -4, and the product becomes -24, which is correct because any triple is negative and we want the least magnitude loss.
Another case is when zeros are present. In an array like $[-1, -2, 0, 3]$, the algorithm compares $3 \cdot (-1) \cdot (-2) = 6$ against $3 \cdot 0 \cdot (-1) = 0$, correctly preferring 6. The presence of zero does not require special handling since it naturally participates in comparisons through the same formulas.
A final subtle case is small arrays where $n = 3$. The algorithm still works because max1, max2, max3 and min1, min2 are all filled during the scan, and both candidate products reduce to the single possible triple.