334. Increasing Triplet Subsequence
Problem Description
You are given an integer array nums
. Your task is to determine if there exists an increasing triplet subsequence in the array.
An increasing triplet subsequence means finding three indices i
, j
, and k
such that:
- The indices are in strictly increasing order:
i < j < k
- The values at these indices are also in strictly increasing order:
nums[i] < nums[j] < nums[k]
If such a triplet exists anywhere in the array, return true
. Otherwise, return false
.
The key insight of the solution is to maintain two variables while scanning through the array:
mi
: tracks the smallest value seen so farmid
: tracks the smallest value that is greater thanmi
As we iterate through each number:
- If we find a number greater than
mid
, we've found our increasing triplet (since we already havemi < mid < current number
) - If the number is less than or equal to
mi
, we updatemi
to this smaller value - Otherwise, the number must be between
mi
andmid
, so we updatemid
This greedy approach works because we're always maintaining the smallest possible values for the first two elements of a potential triplet, maximizing our chances of finding a valid third element.
Intuition
The naive approach would be to check every possible triplet using three nested loops, which would take O(nΒ³)
time. We need something more efficient.
Let's think about what we're really looking for: three numbers in increasing order where their positions also increase. As we scan through the array, at any point, we want to know: "Can I form an increasing triplet ending at the current position?"
To form such a triplet, we need two smaller numbers before the current position. But here's the key insight: we don't need to track all possible pairs of smaller numbers. We only need to track the best possible pair - the one most likely to help us find a valid triplet.
What makes a pair "best"? If we have the smallest possible first element and the smallest possible second element (that's still greater than the first), we maximize our chances of finding a third element that's greater than both.
Think of it like building stairs:
- We want the first step (
mi
) to be as low as possible - We want the second step (
mid
) to be as low as possible while still being higher than the first - Then any number higher than the second step completes our staircase
This leads us to the greedy strategy: maintain two variables representing the smallest and second-smallest values we've seen so far in a way that preserves the increasing relationship. When we encounter a number:
- If it's bigger than our second-smallest (
mid
), we've found our triplet! - If it's the smallest we've seen, update our first step
- If it's between our first and second steps, update our second step to make it lower (better for future comparisons)
This way, we only need one pass through the array with O(1)
space, achieving O(n)
time complexity.
Solution Approach
We implement the greedy strategy using two variables to track the smallest possible values for a potential increasing triplet:
-
Initialize two trackers:
mi = inf
: Represents the smallest value seen so far (potential first element of triplet)mid = inf
: Represents the smallest value greater thanmi
(potential second element of triplet)
-
Iterate through each number in the array:
for num in nums:
-
Check three conditions for each number:
Condition 1: Found the third element
if num > mid: return True
If the current number is greater than
mid
, we've found our increasing triplet. Why? Because:- We already have
mi < mid
(by construction) - Now we have
num > mid
- Therefore:
mi < mid < num
forms a valid triplet
Condition 2: Update the smallest value
if num <= mi: mi = num
If the current number is less than or equal to
mi
, updatemi
. This keeps our first potential element as small as possible, maximizing chances for finding valid second and third elements later.Condition 3: Update the middle value
else: mid = num
If the number is between
mi
andmid
(greater thanmi
but not greater thanmid
), updatemid
. This maintains the relationshipmi < mid
while keepingmid
as small as possible. - We already have
-
Return false if no triplet found:
return False
If we complete the iteration without finding a valid triplet, return
False
.
The algorithm maintains the invariant that mi
and mid
always represent the best possible pair of values for forming an increasing triplet, where "best" means having the smallest possible values while maintaining mi < mid
.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with the array nums = [2, 1, 5, 0, 4, 6]
.
Initial state:
mi = inf
mid = inf
Step 1: Process num = 2
- Is
2 > mid (inf)
? No - Is
2 <= mi (inf)
? Yes β Updatemi = 2
- State:
mi = 2, mid = inf
Step 2: Process num = 1
- Is
1 > mid (inf)
? No - Is
1 <= mi (2)
? Yes β Updatemi = 1
- State:
mi = 1, mid = inf
Step 3: Process num = 5
- Is
5 > mid (inf)
? No - Is
5 <= mi (1)
? No - Since
5 > mi (1)
and5 <= mid (inf)
β Updatemid = 5
- State:
mi = 1, mid = 5
- We now have a potential pair:
1 < 5
Step 4: Process num = 0
- Is
0 > mid (5)
? No - Is
0 <= mi (1)
? Yes β Updatemi = 0
- State:
mi = 0, mid = 5
- Note: We still keep
mid = 5
even thoughmi
changed. This is crucial -mid
was set whenmi
was 1, and the relationship1 < 5
still exists in the array.
Step 5: Process num = 4
- Is
4 > mid (5)
? No - Is
4 <= mi (0)
? No - Since
4 > mi (0)
and4 <= mid (5)
β Updatemid = 4
- State:
mi = 0, mid = 4
- We improved our middle value to a smaller one while maintaining the increasing property.
Step 6: Process num = 6
- Is
6 > mid (4)
? Yes! β Return True - We found our triplet! While our current
mi = 0
andmid = 4
, the actual triplet in the array is formed by the indices where:- First element: 1 (at index 1)
- Second element: 5 or 4 (at index 2 or 4)
- Third element: 6 (at index 5)
The beauty of this algorithm is that we don't need to track the actual indices. We just need to know that at some point we had values satisfying first < second
, and now we found a third > second
, guaranteeing an increasing triplet exists.
Solution Implementation
1class Solution:
2 def increasingTriplet(self, nums: List[int]) -> bool:
3 """
4 Determines if there exists an increasing triplet subsequence in the array.
5
6 The algorithm maintains two variables to track the smallest and middle values
7 of a potential increasing triplet. It scans through the array once to find
8 if there exists i < j < k such that nums[i] < nums[j] < nums[k].
9
10 Args:
11 nums: List of integers to check for increasing triplet
12
13 Returns:
14 True if an increasing triplet exists, False otherwise
15 """
16 # Initialize the smallest value and middle value to infinity
17 # These represent the first two elements of a potential triplet
18 smallest = float('inf')
19 middle = float('inf')
20
21 # Iterate through each number in the array
22 for current_num in nums:
23 # If we find a number greater than middle, we have found our triplet
24 # This means we have: smallest < middle < current_num
25 if current_num > middle:
26 return True
27
28 # If current number is less than or equal to smallest, update smallest
29 # This ensures we always track the minimum value seen so far
30 if current_num <= smallest:
31 smallest = current_num
32 else:
33 # If current number is greater than smallest but not greater than middle,
34 # update middle to current number
35 # This means we found a valid pair (smallest, current_num)
36 middle = current_num
37
38 # No increasing triplet was found after scanning the entire array
39 return False
40
1class Solution {
2 public boolean increasingTriplet(int[] nums) {
3 int n = nums.length;
4
5 // leftMin[i] stores the minimum value to the left of index i
6 int[] leftMin = new int[n];
7 // rightMax[i] stores the maximum value to the right of index i
8 int[] rightMax = new int[n];
9
10 // Initialize first element (no elements to its left)
11 leftMin[0] = Integer.MAX_VALUE;
12 // Initialize last element (no elements to its right)
13 rightMax[n - 1] = Integer.MIN_VALUE;
14
15 // Build leftMin array: for each position, find minimum value before it
16 for (int i = 1; i < n; i++) {
17 leftMin[i] = Math.min(leftMin[i - 1], nums[i - 1]);
18 }
19
20 // Build rightMax array: for each position, find maximum value after it
21 for (int i = n - 2; i >= 0; i--) {
22 rightMax[i] = Math.max(rightMax[i + 1], nums[i + 1]);
23 }
24
25 // Check if there exists a triplet where leftMin[i] < nums[i] < rightMax[i]
26 // This means we have found three indices j < i < k where nums[j] < nums[i] < nums[k]
27 for (int i = 0; i < n; i++) {
28 if (leftMin[i] < nums[i] && nums[i] < rightMax[i]) {
29 return true;
30 }
31 }
32
33 return false;
34 }
35}
36
1class Solution {
2public:
3 bool increasingTriplet(vector<int>& nums) {
4 // Initialize two variables to track the smallest and middle values
5 // in a potential increasing triplet subsequence
6 int smallest = INT_MAX; // Smallest value seen so far
7 int middle = INT_MAX; // Second smallest value that comes after 'smallest'
8
9 // Iterate through each number in the array
10 for (int current : nums) {
11 // If current number is greater than middle, we found an increasing triplet
12 // (smallest < middle < current)
13 if (current > middle) {
14 return true;
15 }
16
17 // If current number is less than or equal to smallest,
18 // update smallest to potentially form a better triplet
19 if (current <= smallest) {
20 smallest = current;
21 }
22 // If current is between smallest and middle,
23 // update middle (we have smallest < current)
24 else {
25 middle = current;
26 }
27 }
28
29 // No increasing triplet found in the entire array
30 return false;
31 }
32};
33
1/**
2 * Determines if there exists an increasing triplet subsequence in the array.
3 * An increasing triplet means there exist indices i < j < k such that nums[i] < nums[j] < nums[k].
4 *
5 * @param nums - The input array of numbers
6 * @returns true if an increasing triplet exists, false otherwise
7 */
8function increasingTriplet(nums: number[]): boolean {
9 const arrayLength: number = nums.length;
10
11 // Array must have at least 3 elements to form a triplet
12 if (arrayLength < 3) {
13 return false;
14 }
15
16 // Track the smallest value seen so far
17 let firstSmallest: number = nums[0];
18
19 // Track the second smallest value that comes after firstSmallest
20 let secondSmallest: number = Number.MAX_SAFE_INTEGER;
21
22 // Iterate through each number in the array
23 for (const currentNumber of nums) {
24 if (currentNumber <= firstSmallest) {
25 // Update the smallest value
26 firstSmallest = currentNumber;
27 } else if (currentNumber <= secondSmallest) {
28 // Found a number greater than firstSmallest but less than or equal to secondSmallest
29 // This becomes our new second smallest
30 secondSmallest = currentNumber;
31 } else {
32 // Found a number greater than both firstSmallest and secondSmallest
33 // This forms an increasing triplet
34 return true;
35 }
36 }
37
38 // No increasing triplet found
39 return false;
40}
41
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input array nums
. The algorithm makes a single pass through the array, performing constant-time operations (comparisons and assignments) for each element.
Space Complexity: O(1)
- The algorithm uses only two extra variables (mi
and mid
) to track the smallest and middle values of a potential increasing triplet, regardless of the input size. No additional data structures that scale with input size are used.
Common Pitfalls
Pitfall 1: Misunderstanding the Variable Updates
The Problem:
A common misconception is thinking that when we update mi
to a new smaller value, we lose track of the previous valid pair (mi, mid)
. Students often worry: "If I update mi
after finding a valid mid
, won't I break the existing relationship?"
Example of the Confusion:
Consider the array [5, 1, 6, 2, 7]
:
- Initially:
mi = 5
,mid = inf
- After
1
:mi = 1
(updated from 5) - After
6
:mid = 6
(nowmi = 1, mid = 6
) - After
2
:mi = 1, mid = 2
(updated mid) - After
7
: Found triplet! (1 < 2 < 7
)
Some might incorrectly think: "Wait, but the actual triplet in order is 5 < 6 < 7
, not 1 < 2 < 7
. Is this valid?"
The Solution: The algorithm doesn't track the actual indices or preserve the exact triplet elements. Instead, it maintains that:
- There exists some value before the current position that could be the first element
- There exists another value after that first one that could be the second element
- We're looking for a third element greater than both
The key insight: Once we establish that a valid (mi, mid)
pair exists somewhere in the array, updating mi
to a smaller value doesn't invalidate that a pair existed - it just gives us a better chance of finding a third element.
Pitfall 2: Incorrect Condition Order
The Problem: Changing the order of the if-conditions can break the algorithm. For instance:
# WRONG ORDER for num in nums: if num <= mi: mi = num elif num <= mid: mid = num elif num > mid: return True
Why This Fails:
The condition num > mid
should be checked first. If we update mi
or mid
before checking if num
could be our third element, we might miss valid triplets.
The Solution: Always check conditions in this specific order:
- First check if
num > mid
(found triplet) - Then check if
num <= mi
(update smallest) - Finally, handle the middle case (update mid)
Pitfall 3: Using Strict Inequality for the First Update
The Problem:
Using num < mi
instead of num <= mi
:
# INCORRECT if num < mi: # Should be num <= mi mi = num
Why This Matters:
Consider [1, 1, 1, 1, 2, 3]
. If we use strict inequality:
- After first
1
:mi = 1
- After second
1
: We don't updatemi
, so we setmid = 1
- This incorrectly establishes
mi = 1, mid = 1
when we needmi < mid
The Solution:
Use num <= mi
to ensure duplicate values don't create false pairs. When we see a value equal to mi
, we should update mi
rather than mid
to maintain the strict inequality requirement between elements of the triplet.
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!