LeetCode 2907 - Maximum Profitable Triplets With Increasing Prices I
This problem asks us to select exactly three items from a store while satisfying both an index ordering condition and a
Difficulty: 🟡 Medium
Topics: Array, Binary Indexed Tree, Segment Tree
Solution
LeetCode 2907 - Maximum Profitable Triplets With Increasing Prices I
Problem Understanding
This problem asks us to select exactly three items from a store while satisfying both an index ordering condition and a strictly increasing price condition.
We are given two arrays:
prices[i]represents the price of theith itemprofits[i]represents the profit earned from selecting theith item
We must choose indices i, j, and k such that:
i < j < kprices[i] < prices[j] < prices[k]
If these conditions are satisfied, the total profit becomes:
$$profits[i] + profits[j] + profits[k]$$
Our goal is to maximize this total profit. If no valid triplet exists, we must return -1.
The important detail is that both the indices and prices must increase strictly. Increasing indices alone are not enough, and increasing prices alone are not enough. Both conditions must hold simultaneously.
The constraints are relatively small:
n <= 2000
This immediately tells us that an O(n^2) solution is acceptable because:
$$2000^2 = 4,000,000$$
which is manageable.
However, an O(n^3) brute-force approach would require up to:
$$2000^3 = 8,000,000,000$$
operations, which is far too slow.
Several edge cases are important:
- Arrays may already be strictly decreasing, making any valid triplet impossible.
- Multiple items may have high profits but incompatible prices.
- The optimal triplet may not involve the globally highest profits.
- Prices must be strictly increasing, equal prices cannot be used together.
- There may be only one valid triplet in the entire array.
The problem guarantees:
- Both arrays have the same length.
- All values are positive integers.
- At least three elements exist.
Approaches
Brute Force Approach
The most direct solution is to try every possible triplet (i, j, k) where:
i < j < k
For every triplet, we check whether:
$$prices[i] < prices[j] < prices[k]$$
If the condition holds, we compute the profit sum and track the maximum.
This approach is correct because it exhaustively evaluates every valid possibility. No triplet can be missed.
However, there are three nested loops, so the time complexity becomes:
$$O(n^3)$$
With n = 2000, this is too slow.
Optimal Approach
The key observation is that each valid triplet has a middle element j.
If we fix j, then:
- We need the best valid left element
i - We need the best valid right element
k
Specifically:
i < jandprices[i] < prices[j]k > jandprices[k] > prices[j]
For every position j, we can scan:
- Left side to find the maximum possible left profit
- Right side to find the maximum possible right profit
Then the candidate answer becomes:
$$bestLeft + profits[j] + bestRight$$
This reduces the complexity to O(n^2).
Since n <= 2000, this is efficient enough.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n³) | O(1) | Checks every possible triplet |
| Optimal | O(n²) | O(1) | Treats each index as the middle element |
Algorithm Walkthrough
- Initialize the answer as
-1.
We use -1 because the problem requires returning -1 when no valid triplet exists.
2. Iterate through every index j as the middle element.
The middle element determines the structure of the triplet. Every valid triplet must have exactly one middle position.
3. Search the left side of j.
For every index i < j, check whether:
$$prices[i] < prices[j]$$
If valid, update the maximum left profit.
4. Search the right side of j.
For every index k > j, check whether:
$$prices[k] > prices[j]$$
If valid, update the maximum right profit. 5. If both sides contain valid candidates, compute the total profit.
The total becomes:
$$leftBest + profits[j] + rightBest$$ 6. Update the global maximum answer. 7. After processing all middle positions, return the final answer.
Why it works
Every valid triplet has a unique middle index j. By fixing j, the problem separates into two independent subproblems:
- Find the best valid left element
- Find the best valid right element
Because we examine all possible middle positions and always choose the maximum compatible profits on both sides, the algorithm guarantees the optimal total profit is found.
Python Solution
from typing import List
class Solution:
def maxProfit(self, prices: List[int], profits: List[int]) -> int:
n = len(prices)
best_profit = -1
for middle in range(n):
left_best = -1
right_best = -1
# Find best left candidate
for left in range(middle):
if prices[left] < prices[middle]:
left_best = max(left_best, profits[left])
# Find best right candidate
for right in range(middle + 1, n):
if prices[right] > prices[middle]:
right_best = max(right_best, profits[right])
# If both sides are valid, update answer
if left_best != -1 and right_best != -1:
total_profit = left_best + profits[middle] + right_best
best_profit = max(best_profit, total_profit)
return best_profit
The implementation directly follows the algorithm.
The outer loop treats every index as the middle element of the triplet.
For each middle position:
- The first inner loop searches leftward for the highest profit whose price is smaller.
- The second inner loop searches rightward for the highest profit whose price is larger.
If both valid sides exist, we combine their profits with the middle profit and update the answer.
The implementation uses only a few integer variables, so the extra memory usage stays constant.
Go Solution
package main
func maxProfit(prices []int, profits []int) int {
n := len(prices)
bestProfit := -1
for middle := 0; middle < n; middle++ {
leftBest := -1
rightBest := -1
// Find best left candidate
for left := 0; left < middle; left++ {
if prices[left] < prices[middle] {
if profits[left] > leftBest {
leftBest = profits[left]
}
}
}
// Find best right candidate
for right := middle + 1; right < n; right++ {
if prices[right] > prices[middle] {
if profits[right] > rightBest {
rightBest = profits[right]
}
}
}
// Update answer if valid triplet exists
if leftBest != -1 && rightBest != -1 {
totalProfit := leftBest + profits[middle] + rightBest
if totalProfit > bestProfit {
bestProfit = totalProfit
}
}
}
return bestProfit
}
The Go implementation is structurally identical to the Python solution.
One small difference is that Go does not provide a built in max() function for integers, so comparisons are written explicitly using if statements.
Integer overflow is not a concern because the problem guarantees all results fit inside a signed 32-bit integer.
Worked Examples
Example 1
prices = [10,2,3,4]
profits = [100,2,7,10]
We examine each middle position.
| Middle Index | Middle Price | Best Left Profit | Best Right Profit | Total |
|---|---|---|---|---|
| 0 | 10 | none | none | invalid |
| 1 | 2 | none | 10 | invalid |
| 2 | 3 | 2 | 10 | 19 |
| 3 | 4 | 7 | none | invalid |
The best valid answer is:
$$2 + 7 + 10 = 19$$
Result:
19
Example 2
prices = [1,2,3,4,5]
profits = [1,5,3,4,6]
All prices are increasing, so many triplets are valid.
| Middle Index | Middle Profit | Best Left Profit | Best Right Profit | Total |
|---|---|---|---|---|
| 1 | 5 | 1 | 6 | 12 |
| 2 | 3 | 5 | 6 | 14 |
| 3 | 4 | 5 | 6 | 15 |
Maximum result:
$$5 + 4 + 6 = 15$$
Result:
15
Example 3
prices = [4,3,2,1]
profits = [33,20,19,87]
Prices are strictly decreasing.
No valid triplet can satisfy:
$$prices[i] < prices[j] < prices[k]$$
Result:
-1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n²) | Each middle index scans both left and right sides |
| Space | O(1) | Only a few variables are used |
The outer loop runs n times. For each middle index, we may scan up to n elements on the left and right combined. This gives quadratic complexity overall.
No additional arrays or data structures are required, so the extra memory usage remains constant.
Test Cases
from typing import List
class Solution:
def maxProfit(self, prices: List[int], profits: List[int]) -> int:
n = len(prices)
best_profit = -1
for middle in range(n):
left_best = -1
right_best = -1
for left in range(middle):
if prices[left] < prices[middle]:
left_best = max(left_best, profits[left])
for right in range(middle + 1, n):
if prices[right] > prices[middle]:
right_best = max(right_best, profits[right])
if left_best != -1 and right_best != -1:
best_profit = max(
best_profit,
left_best + profits[middle] + right_best
)
return best_profit
sol = Solution()
assert sol.maxProfit([10,2,3,4], [100,2,7,10]) == 19
# Basic example with exactly one valid triplet
assert sol.maxProfit([1,2,3,4,5], [1,5,3,4,6]) == 15
# Fully increasing prices
assert sol.maxProfit([4,3,2,1], [33,20,19,87]) == -1
# No valid triplet exists
assert sol.maxProfit([1,3,2,4], [5,100,6,7]) == 112
# Best middle choice is not the largest price
assert sol.maxProfit([1,2,2,3], [1,10,20,30]) == 51
# Equal prices cannot both be used
assert sol.maxProfit([5,1,6,2,7], [10,20,30,40,50]) == 110
# Non-contiguous valid triplet
assert sol.maxProfit([1,2,3], [10,20,30]) == 60
# Smallest valid array size
assert sol.maxProfit([3,1,2], [5,6,7]) == -1
# Minimum size but invalid ordering
Test Summary
| Test | Why |
|---|---|
[10,2,3,4] |
Validates basic functionality |
[1,2,3,4,5] |
Tests fully increasing prices |
[4,3,2,1] |
Tests impossible case |
[1,3,2,4] |
Tests nontrivial middle selection |
[1,2,2,3] |
Verifies strict inequality handling |
[5,1,6,2,7] |
Tests scattered valid indices |
[1,2,3] |
Smallest valid input size |
[3,1,2] |
Smallest invalid configuration |
Edge Cases
One important edge case occurs when the prices array is strictly decreasing. In this scenario, no triplet can ever satisfy the increasing price requirement. A naive implementation might incorrectly assume some triplet always exists. This solution safely handles the situation by initializing the answer to -1 and updating it only when both valid left and right candidates exist.
Another important case involves duplicate prices. The condition requires strictly increasing prices, not nondecreasing prices. For example, prices like [1,2,2,3] cannot use both 2 values together. The implementation correctly uses strict comparisons:
prices[left] < prices[middle]
and
prices[right] > prices[middle]
so duplicates are automatically excluded.
A third edge case occurs when the highest profit items do not form a valid increasing sequence. A greedy approach that simply picks the top three profits would fail. The algorithm avoids this mistake by validating every candidate against the price ordering rules before considering its profit contribution.