LeetCode 2873 - Maximum Value of an Ordered Triplet I
You are given an integer array nums and must choose three indices (i, j, k) such that: - i < j < k - The triplet value is (nums[i] - nums[j]) nums[k] Among all valid ordered triplets, we want the maximum possible value.
Difficulty: 🟢 Easy
Topics: Array
Solution
LeetCode 2873 - Maximum Value of an Ordered Triplet I
Problem Understanding
You are given an integer array nums and must choose three indices (i, j, k) such that:
i < j < k- The triplet value is
(nums[i] - nums[j]) * nums[k]
Among all valid ordered triplets, we want the maximum possible value. If every possible triplet produces a negative value, we return 0 instead.
In other words, we need to find three positions that maximize the expression:
$$(nums[i] - nums[j]) \times nums[k]$$
while preserving the ordering constraint i < j < k.
The input array contains positive integers, and its length is between 3 and 100. Since the array is relatively small, even cubic algorithms are technically feasible. However, it is still useful to identify the underlying pattern and derive a more efficient solution.
A few observations are important:
- Since all
nums[k]values are positive, maximizing the expression largely depends on maximizing(nums[i] - nums[j]). - If
(nums[i] - nums[j])is negative, multiplying by a positivenums[k]keeps the result negative. - The problem explicitly states that if the best value is negative, we should return
0. - We must respect the ordering requirement, so we cannot freely choose any three values from the array.
Important edge cases include arrays where every increasing pair produces a negative difference, arrays where the maximum value comes from an early large element and a later small element, and arrays containing repeated values that may produce zero-valued triplets.
Approaches
Brute Force
The most direct solution is to enumerate every possible triplet (i, j, k).
For each valid combination:
- Compute
(nums[i] - nums[j]) * nums[k]. - Track the maximum value seen.
- Return
max(0, answer).
Since there are three nested loops, the time complexity is O(n³).
This approach is correct because it explicitly evaluates every valid triplet and therefore cannot miss the optimal answer. However, it performs unnecessary repeated work because many calculations involve the same prefixes and suffixes.
Key Insight
For a fixed middle index j, the expression can be rewritten as:
$$(\max_{i<j} nums[i] - nums[j]) \times \max_{k>j} nums[k]$$
When j is fixed:
- The best
iis simply the largest value appearing beforej. - The best
kis simply the largest value appearing afterj.
Therefore, instead of checking every triplet, we can:
- Maintain the maximum value seen to the left of
j. - Know the maximum value available to the right of
j. - Compute the candidate value for every possible
j.
This reduces the problem to linear processing after some preprocessing.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n³) | O(1) | Tries every valid triplet |
| Optimal | O(n) | O(n) | Uses prefix maximums and suffix maximums |
Algorithm Walkthrough
Optimal Algorithm
- Create a suffix maximum array
suffix_max.
For every index i, store the largest value that appears at or after position i. This allows us to instantly determine the best possible k for any middle position j.
2. Build suffix_max from right to left.
The recurrence is:
suffix_max[i] = max(nums[i], suffix_max[i + 1])
This ensures each position knows the largest value available in its suffix.
3. Initialize left_max as nums[0].
This variable stores the largest value encountered before the current middle index.
4. Iterate through all valid middle indices j.
Since i < j < k, the middle index can range from 1 to n - 2.
5. For each j, compute the best possible triplet value.
The largest valid value before j is left_max.
The largest valid value after j is suffix_max[j + 1].
Therefore:
candidate = (left_max - nums[j]) * suffix_max[j + 1]
- Update the global answer.
Keep the maximum candidate value seen so far.
7. Update left_max.
After processing j, include nums[j] in the prefix:
left_max = max(left_max, nums[j])
- Return the answer.
Since the answer starts at 0, negative values are automatically ignored.
Why it works
For every middle position j, the expression depends on two independent choices:
- A value before
j, contributingnums[i]. - A value after
j, contributingnums[k].
Because nums[k] is always positive, the optimal choice for a fixed j is the largest possible value before j and the largest possible value after j. The algorithm explicitly evaluates this optimal choice for every valid middle index, guaranteeing that the global maximum triplet value is found.
Python Solution
from typing import List
class Solution:
def maximumTripletValue(self, nums: List[int]) -> int:
n = len(nums)
suffix_max = [0] * n
suffix_max[-1] = nums[-1]
for i in range(n - 2, -1, -1):
suffix_max[i] = max(nums[i], suffix_max[i + 1])
left_max = nums[0]
answer = 0
for j in range(1, n - 1):
candidate = (left_max - nums[j]) * suffix_max[j + 1]
answer = max(answer, candidate)
left_max = max(left_max, nums[j])
return answer
The implementation begins by constructing the suffix_max array. Each position stores the largest element available from that index to the end of the array.
Next, left_max tracks the largest value encountered before the current middle index. As we scan from left to right, this variable always represents the optimal choice for index i.
For every valid middle index j, we compute the best possible triplet value using the largest value before j and the largest value after j. The answer is updated whenever a larger value is found.
Finally, left_max is updated to include the current element before moving to the next iteration.
Go Solution
func maximumTripletValue(nums []int) int64 {
n := len(nums)
suffixMax := make([]int, n)
suffixMax[n-1] = nums[n-1]
for i := n - 2; i >= 0; i-- {
if nums[i] > suffixMax[i+1] {
suffixMax[i] = nums[i]
} else {
suffixMax[i] = suffixMax[i+1]
}
}
leftMax := nums[0]
var answer int64 = 0
for j := 1; j < n-1; j++ {
candidate := int64(leftMax-nums[j]) * int64(suffixMax[j+1])
if candidate > answer {
answer = candidate
}
if nums[j] > leftMax {
leftMax = nums[j]
}
}
return answer
}
The Go solution follows exactly the same logic as the Python implementation. One important difference is the use of int64 for the result and intermediate multiplication. Since values can be as large as 10^6, the product may reach approximately 10^12, which exceeds the range of a 32-bit integer. Converting operands to int64 before multiplication avoids overflow.
Worked Examples
Example 1
nums = [12, 6, 1, 2, 7]
Suffix Maximum Array
| Index | Value | Suffix Max |
|---|---|---|
| 0 | 12 | 12 |
| 1 | 6 | 7 |
| 2 | 1 | 7 |
| 3 | 2 | 7 |
| 4 | 7 | 7 |
Iteration
| j | left_max | nums[j] | best right value | candidate | answer |
|---|---|---|---|---|---|
| 1 | 12 | 6 | 7 | (12-6)×7=42 | 42 |
| 2 | 12 | 1 | 7 | (12-1)×7=77 | 77 |
| 3 | 12 | 2 | 7 | (12-2)×7=70 | 77 |
Final answer: 77
Example 2
nums = [1, 10, 3, 4, 19]
Suffix Maximum Array
| Index | Value | Suffix Max |
|---|---|---|
| 0 | 1 | 19 |
| 1 | 10 | 19 |
| 2 | 3 | 19 |
| 3 | 4 | 19 |
| 4 | 19 | 19 |
Iteration
| j | left_max | nums[j] | best right value | candidate | answer |
|---|---|---|---|---|---|
| 1 | 1 | 10 | 19 | -171 | 0 |
| 2 | 10 | 3 | 19 | 133 | 133 |
| 3 | 10 | 4 | 19 | 114 | 133 |
Final answer: 133
Example 3
nums = [1, 2, 3]
Suffix Maximum Array
| Index | Value | Suffix Max |
|---|---|---|
| 0 | 1 | 3 |
| 1 | 2 | 3 |
| 2 | 3 | 3 |
Iteration
| j | left_max | nums[j] | best right value | candidate | answer |
|---|---|---|---|---|---|
| 1 | 1 | 2 | 3 | -3 | 0 |
Final answer: 0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | One pass to build suffix maxima and one pass to evaluate candidates |
| Space | O(n) | Suffix maximum array stores one value per index |
The algorithm performs only two linear scans of the array. Every operation inside those scans is constant time, resulting in overall O(n) time complexity. The extra memory comes solely from the suffix maximum array, which requires O(n) space.
Test Cases
from typing import List
s = Solution()
assert s.maximumTripletValue([12, 6, 1, 2, 7]) == 77 # example 1
assert s.maximumTripletValue([1, 10, 3, 4, 19]) == 133 # example 2
assert s.maximumTripletValue([1, 2, 3]) == 0 # example 3
assert s.maximumTripletValue([5, 5, 5]) == 0 # all equal values
assert s.maximumTripletValue([10, 1, 10]) == 90 # minimum valid length
assert s.maximumTripletValue([100, 1, 1, 100]) == 9900 # large positive difference
assert s.maximumTripletValue([1, 100, 1, 100]) == 9900 # best triplet not starting at index 0
assert s.maximumTripletValue([9, 8, 7, 6]) == 18 # decreasing array
assert s.maximumTripletValue([1, 3, 2, 5]) == 5 # mixed ordering
assert s.maximumTripletValue([1, 2, 1]) == 0 # negative result only
assert s.maximumTripletValue([1000000, 1, 1000000]) == 999999000000 # large values
Test Summary
| Test | Why |
|---|---|
[12,6,1,2,7] |
Validates example 1 |
[1,10,3,4,19] |
Validates example 2 |
[1,2,3] |
Validates negative result handling |
[5,5,5] |
Ensures equal values produce zero |
[10,1,10] |
Smallest allowed array length |
[100,1,1,100] |
Large positive difference case |
[1,100,1,100] |
Best prefix maximum appears later |
[9,8,7,6] |
Decreasing sequence |
[1,3,2,5] |
General mixed values |
[1,2,1] |
Only negative candidate exists |
[1000000,1,1000000] |
Verifies large-number arithmetic |
Edge Cases
All Triplets Produce Negative Values
Consider:
[1, 2, 3]
The only triplet produces (1 - 2) * 3 = -3. A common bug is returning the largest computed value directly, which would incorrectly return -3. This implementation initializes the answer to 0, ensuring negative values are ignored automatically.
Repeated Values
Consider:
[5, 5, 5]
Every triplet produces (5 - 5) * 5 = 0. Some implementations incorrectly assume a positive difference must exist. Our solution correctly computes the candidate value and allows zero to remain the maximum.
Very Large Numbers
Consider:
[1000000, 1, 1000000]
The result is:
(1000000 - 1) * 1000000 = 999999000000
This exceeds the range of a 32-bit integer. The Go implementation explicitly uses int64 for multiplication and storage of the answer, preventing overflow.
Best Prefix Maximum Appears Before the Current Middle Index
Consider:
[1, 100, 1, 100]
The optimal triplet uses the value 100 at index 1 as the left element. Maintaining left_max ensures that for every middle index, we always know the largest value available before it. This guarantees that the optimal i is considered without checking every previous position.