Increasing Triplet Subsequence
The Problem
LeetCode 334 asks you to determine whether a given integer array contains an increasing triplet subsequence: three indices i < j < k such that nums[i] < nums[j] < nums[k]. You don't need to find the actual values, just return True if such indices exist, or False if they don't.
The brute-force O(n^3) approach checks every combination of three indices. O(n^2) is also possible by fixing j and scanning both sides. But you can do it in a single pass, O(n) time, O(1) space, and that is what makes this problem interesting.
The diagram shows nums = [2, 1, 5, 0, 4, 6]. The values 1, 4, and 6 at indices 1, 4, and 5 form a valid triplet: each index is larger than the last, and each value is larger than the last.
The Greedy Insight
You keep two sentinels:
first-- the smallest number you have seen so far.second-- the smallest number you have seen that is greater than some previousfirst.
If you ever encounter a number that is greater than second, you have your triplet: something came before second (namely first) that was smaller, and now you have found something larger than both.
Here is the key subtlety that trips people up: when first updates to a new, lower value after second has already been set, that is fine. second still serves as proof that at some earlier point in the array there existed a number smaller than it. You don't need first to literally be to the left of second in your current state -- the history of the array guarantees it.
The Solution
def increasingTriplet(nums):
first = second = float('inf')
for num in nums:
if num <= first:
first = num
elif num <= second:
second = num
else:
return True
return False
Walk through the logic branch by branch:
num <= first: this is the smallest value seen yet, so updatefirst.num <= second(and implicitlynum > first): this number sits betweenfirstandsecond, so it becomes the new, tightersecond.- Otherwise:
num > second > first, so you have a triplet. Return immediately.
Step-by-Step Walkthrough
Let's trace through [2, 1, 5, 0, 4, 6] one number at a time:
Step 1: num = 2. 2 <= first (inf), so first = 2.
first tracks the smallest number seen so far. It drops from inf to 2.
Step 2: num = 1. 1 <= first (2), so first = 1.
1 is smaller than 2, so first updates to 1. A lower starting point.
Step 3: num = 5. 5 > first (1) and 5 > second (inf)? No — 5 <= inf, so second = 5.
5 is larger than first (1) but still fits as second. second = 5.
Step 4: num = 0. 0 <= first (1), so first = 0.
0 is less than first. first = 0. second stays at 5 — it still encodes that a valid pair exists.
Step 5: num = 4. 4 > first (0) and 4 <= second (5), so second = 4.
4 sits between first and second. We update second = 4. A tighter upper bound.
Step 6: num = 6. 6 > first (0) and 6 > second (4). Return True!
6 beats second. We have a triplet: first < second < 6. Done.
Notice step 4: when num = 0, first drops to 0 even though second is already 5. That can feel wrong -- surely 0 isn't to the left of 5 in the final triplet. But it doesn't matter. The fact that second = 5 was set tells us there was some earlier value smaller than 5. If anything later beats 5, a valid triplet exists. When 4 comes along in step 5 it shrinks second to 4, and then 6 finishes the job.
Complexity
| Complexity | |
|---|---|
| Time | O(n) |
| Space | O(1) |
A single pass through the array, two variables. That is as lean as it gets.
Building Blocks
This problem combines two ideas:
- Greedy tracking with sentinels. Instead of remembering the whole prefix, you distil it to two numbers that capture exactly the information you need.
- Invariant reasoning. The invariant is: if
secondhas a finite value, then there exist at least two numbers in the prefix of the array, in increasing order, whose maximum issecond. The proof is inductive over each assignment tosecond.
If you are comfortable with Boyer-Moore voting or the Jump Game, you have already seen this style of "carry the minimum information forward" thinking. It shows up whenever you can reformulate a search problem as a running state machine.
Edge Cases
All decreasing. In [5, 4, 3, 2, 1], every new number updates first downward. second never gets a finite value. Return False.
Length less than 3. With fewer than three elements you cannot have three distinct indices. The loop simply never hits the else branch because second stays infinite. Return False. (The problem guarantees len(nums) >= 1, so you don't need an explicit guard.)
Duplicates. The conditions use <= for both first and second, so equal values slot into first or second without triggering a false positive. A triplet requires strictly increasing values, and the else branch only fires when num > second, which is strictly greater.
Single-element runs. [1, 1, 1, 2, 2, 2]: the 1s keep refreshing first, the first 2 sets second = 2 (since 2 > first = 1 but 2 <= inf), and no number beats 2. Return False.
From Understanding to Recall
The insight here is worth committing to memory because it reappears in variants -- "increasing quadruplet", "longest increasing subsequence in O(n log n)", patience sorting. The mnemonic:
Carry two thresholds. Beat the first, become the second. Beat the second, you win.
When you revisit this problem in a week, ask yourself: what does second actually represent? Answering that question correctly in under ten seconds means the solution will flow naturally from the invariant. Spaced repetition works best when you test your understanding of why, not just what.
Related Posts
- Longest Increasing Subsequence -- the general version of this problem, solved in O(n log n) with patience sorting.
- Jump Game -- another greedy single-pass problem where you track the furthest reachable index.
- Three Sum -- a sorted two-pointer approach for finding triplets that sum to zero.