LeetCode 3073 - Maximum Increasing Triplet Value
We are given an integer array nums, and we must find three indices (i, j, k) such that: - i < j < k - nums[i] < nums[j] < nums[k] Among all valid increasing triplets, we want to maximize the expression: The task is not to maximize the sum of the triplet.
Difficulty: 🟡 Medium
Topics: Array, Ordered Set
Solution
Problem Understanding
We are given an integer array nums, and we must find three indices (i, j, k) such that:
i < j < knums[i] < nums[j] < nums[k]
Among all valid increasing triplets, we want to maximize the expression:
$$nums[i] - nums[j] + nums[k]$$
The task is not to maximize the sum of the triplet. Instead, the middle element is subtracted, which changes the optimization strategy significantly.
The input array can contain up to 10^5 elements, and each value can be as large as 10^9. These constraints immediately rule out any cubic time solution because checking every possible triplet would take far too long.
The problem guarantees that at least one valid increasing triplet exists, so we never need to handle the case where no answer is possible.
The important challenge is balancing the three values correctly:
- We want
nums[k]to be as large as possible because it is added. - We want
nums[j]to be as small as possible because it is subtracted. - We still need
nums[i] < nums[j] < nums[k].
A naive implementation may incorrectly focus only on the largest increasing triplet values without accounting for the subtraction of the middle element.
Another subtle issue is that the smallest valid nums[i] is usually best because it contributes positively while also making it easier to satisfy the increasing condition.
Approaches
Brute Force
The most direct solution is to try every possible triplet (i, j, k).
We use three nested loops:
- The first loop chooses
i - The second loop chooses
j - The third loop chooses
k
For every triplet, we check whether:
$$nums[i] < nums[j] < nums[k]$$
If the condition is valid, we compute:
$$nums[i] - nums[j] + nums[k]$$
and update the maximum answer.
This approach is correct because it exhaustively evaluates every valid triplet. However, its time complexity is far too large.
With n = 10^5, an O(n^3) algorithm would require roughly:
$$(10^5)^3 = 10^{15}$$
operations, which is completely infeasible.
Key Insight for the Optimal Solution
We can rewrite the target expression as:
$$(nums[i] - nums[j]) + nums[k]$$
For every position j, we need:
- The smallest valid value before
jthat is less thannums[j] - The largest valid value after
jthat is greater thannums[j]
If we know these efficiently, then every index j can act as the middle element of the triplet.
The critical observation is:
- Since
nums[i]is added positively, the bestiis actually the smallest valid value beforej - Since
nums[k]is added positively, the bestkis the largest valid value afterj
Thus, for each j, we need:
$$\min(nums[i]) \text{ where } i < j \text{ and } nums[i] < nums[j]$$
and
$$\max(nums[k]) \text{ where } k > j \text{ and } nums[k] > nums[j]$$
We can preprocess suffix maximums to efficiently obtain the best k.
For the left side, we only need the minimum value seen so far because if the minimum value is smaller than nums[j], it is always the best choice.
This leads to a linear time solution.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n³) | O(1) | Checks every possible triplet |
| Optimal | O(n) | O(n) | Uses prefix minimum and suffix maximum |
Algorithm Walkthrough
- Create a suffix maximum array.
For every index i, store the maximum value from i to the end of the array.
This allows us to quickly determine the largest possible nums[k] for any middle index j.
2. Initialize a running minimum value.
As we scan from left to right, maintain the smallest number encountered so far.
This represents the best possible nums[i].
3. Iterate through the array using each position as the middle element j.
For every index j:
- Check whether the current minimum value is smaller than
nums[j] - Check whether the suffix maximum after
jis larger thannums[j]
If both conditions hold, then a valid increasing triplet exists. 4. Compute the triplet value.
Using:
$$minLeft - nums[j] + maxRight$$
where:
minLeftis the smallest valid left valuemaxRightis the largest valid right value
- Update the global maximum answer.
- Update the running minimum.
After processing index j, update the prefix minimum using the current element.
Why it works
For a fixed middle element nums[j], the expression is:
$$nums[i] - nums[j] + nums[k]$$
The only constraints are:
$$nums[i] < nums[j] < nums[k]$$
To maximize the expression:
- We should maximize
nums[k] - We should minimize
nums[j] - We should choose the smallest valid
nums[i]
The suffix maximum array guarantees we always choose the best possible k, and the running prefix minimum guarantees we always choose the best possible i.
Since every index is considered exactly once as the middle element, the algorithm evaluates all optimal candidates.
Python Solution
from typing import List
class Solution:
def maximumTripletValue(self, nums: List[int]) -> int:
n = len(nums)
# suffix_max[i] = maximum value from i to n-1
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])
min_left = nums[0]
answer = 0
for j in range(1, n - 1):
max_right = suffix_max[j + 1]
if min_left < nums[j] < max_right:
value = min_left - nums[j] + max_right
answer = max(answer, value)
min_left = min(min_left, nums[j])
return answer
The implementation begins by constructing the suffix_max array. Each position stores the largest value that appears at or after that index. This allows constant time lookup of the best possible right element for every middle index.
Next, the algorithm scans the array from left to right while maintaining min_left, the smallest value seen so far.
For every middle position j, the algorithm retrieves:
min_leftmax_right
It then verifies that:
min_left < nums[j] < max_right
This ensures a valid increasing triplet exists.
If valid, the triplet value is computed and compared against the current maximum.
Finally, min_left is updated so future iterations have access to the smallest prefix value.
Go Solution
func maximumTripletValue(nums []int) int {
n := len(nums)
// suffixMax[i] = maximum value from i to n-1
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]
}
}
minLeft := nums[0]
answer := 0
for j := 1; j < n-1; j++ {
maxRight := suffixMax[j+1]
if minLeft < nums[j] && nums[j] < maxRight {
value := minLeft - nums[j] + maxRight
if value > answer {
answer = value
}
}
if nums[j] < minLeft {
minLeft = nums[j]
}
}
return answer
}
The Go implementation follows the same algorithmic structure as the Python version.
Slices are used for the suffix maximum array. Since the constraints fit comfortably within Go's standard integer range on modern platforms, no special overflow handling is required.
Go does not provide built in min or max functions for integers, so explicit comparisons are used instead.
Worked Examples
Example 1
Input:
nums = [5, 6, 9]
Step 1: Build suffix maximum array
| Index | Value | suffix_max |
|---|---|---|
| 2 | 9 | 9 |
| 1 | 6 | 9 |
| 0 | 5 | 9 |
Result:
suffix_max = [9, 9, 9]
Step 2: Iterate
Initial state:
min_left = 5
answer = 0
| j | nums[j] | min_left | max_right | Valid? | Value | answer |
|---|---|---|---|---|---|---|
| 1 | 6 | 5 | 9 | Yes | 5 - 6 + 9 = 8 | 8 |
Final answer:
8
Example 2
Input:
nums = [1, 5, 3, 6]
Step 1: Build suffix maximum array
| Index | Value | suffix_max |
|---|---|---|
| 3 | 6 | 6 |
| 2 | 3 | 6 |
| 1 | 5 | 6 |
| 0 | 1 | 6 |
Result:
suffix_max = [6, 6, 6, 6]
Step 2: Iterate
Initial state:
min_left = 1
answer = 0
| j | nums[j] | min_left | max_right | Valid? | Value | answer |
|---|---|---|---|---|---|---|
| 1 | 5 | 1 | 6 | Yes | 1 - 5 + 6 = 2 | 2 |
| 2 | 3 | 1 | 6 | Yes | 1 - 3 + 6 = 4 | 4 |
Final answer:
4
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | One pass to build suffix maximums and one pass to evaluate triplets |
| Space | O(n) | The suffix maximum array stores one value per index |
The algorithm performs only two linear scans of the array. Every operation inside the loops is constant time, so the overall runtime is linear.
The only extra memory used is the suffix maximum array.
Test Cases
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])
min_left = nums[0]
answer = 0
for j in range(1, n - 1):
max_right = suffix_max[j + 1]
if min_left < nums[j] < max_right:
answer = max(answer, min_left - nums[j] + max_right)
min_left = min(min_left, nums[j])
return answer
sol = Solution()
assert sol.maximumTripletValue([5, 6, 9]) == 8 # smallest valid input
assert sol.maximumTripletValue([1, 5, 3, 6]) == 4 # multiple valid triplets
assert sol.maximumTripletValue([1, 2, 3, 4]) == 3 # strictly increasing array
assert sol.maximumTripletValue([2, 1, 5, 0, 4, 6]) == 7 # classic mixed ordering
assert sol.maximumTripletValue([1, 10, 2, 20]) == 19 # best middle element is not largest
assert sol.maximumTripletValue([3, 4, 5]) == 4 # exactly one triplet
assert sol.maximumTripletValue([1, 2, 1000000000]) == 999999999 # large values
assert sol.maximumTripletValue([1, 3, 2, 4, 5]) == 4 # multiple candidate middles
assert sol.maximumTripletValue([1, 100, 2, 3, 200]) == 199 # late large suffix value
Test Summary
| Test | Why |
|---|---|
[5, 6, 9] |
Smallest possible valid array |
[1, 5, 3, 6] |
Multiple valid triplets |
[1, 2, 3, 4] |
Strictly increasing sequence |
[2, 1, 5, 0, 4, 6] |
Mixed ordering and updates to prefix minimum |
[1, 10, 2, 20] |
Best middle value is not the largest |
[3, 4, 5] |
Exactly one valid triplet |
[1, 2, 1000000000] |
Very large numbers |
[1, 3, 2, 4, 5] |
Multiple competing middle indices |
[1, 100, 2, 3, 200] |
Large suffix value appears late |
Edge Cases
One important edge case is the minimum valid array length of three elements. In this situation, there is exactly one possible triplet. The implementation handles this naturally because the loop runs exactly once for the middle index.
Another important case is when the array is strictly increasing. Here, every position can potentially serve as the middle element. The algorithm correctly evaluates each one and selects the maximum expression value.
Arrays with fluctuating values can also be tricky. For example:
[2, 1, 5, 0, 4, 6]
The smallest prefix value changes during iteration, and the best triplet may involve elements that are far apart. The running min_left variable ensures the algorithm always uses the optimal left candidate.
Large integer values are another concern. Since nums[i] can reach 10^9, the computed expression may also become large. Both Python and Go safely handle these values under the problem constraints.
Finally, repeated local peaks can mislead naive greedy strategies. A larger middle value is actually harmful because it is subtracted. The algorithm avoids this mistake by evaluating the actual expression value for every valid middle position instead of greedily selecting large numbers.