Solving tips
- Track two running minima: first = smallest seen, second = smallest value that has something smaller before it; any element beating second proves a triplet.
- Use <= (not <) in the comparisons so duplicate values don't spuriously advance to second.
- Don't be spooked when first ends up positioned after the element that set second β the proof relies only on the history at assignment time, so no false positives occur.
- Aim for O(n) time and O(1) space; this generalizes to the patience-sorting O(n log n) longest-increasing-subsequence idea.
Problem
Given an integer array nums, decide whether there exist three indices i < j < k such that nums[i] < nums[j] < nums[k]. Return True if such an increasing triplet (not necessarily contiguous) exists, False otherwise.
You only need to report existence β you do not have to return the indices.
Examples
nums = [1,2,3,4,5] β True β 1 < 2 < 3 (many triplets work).
nums = [5,4,3,2,1] β False β strictly decreasing, so no increasing triple exists.
nums = [2,1,5,0,4,6] β True β the triplet 1 < 4 < 6 (indices 1, 4, 5) increases.
Constraints
1 <= len(nums) <= 5 * 10^5
-2^31 <= nums[i] <= 2^31 - 1
The half-million bound and the βcan you do it in O(n) time and O(1) space?β follow-up steer you away from the cubic and quadratic solutions.
Think about it first
Hint 1
A triplet needs a "small," a "medium bigger than the small seen earlier," and any "large bigger than that medium." What two running values would you track as you scan left to right?
Hint 2
Keep first = smallest value seen so far, and second = smallest value that has some smaller value before it. If any later number exceeds second, you are done.
Hint 3
Greedily lower first and second whenever you can. It looks suspicious that first might later refer to an element positioned after second β but convince yourself that once second was set, a valid first genuinely existed before it, so seeing any value greater than second proves a triplet.
TL;DR
Track the smallest value (first) and the smallest value that has something smaller before it (second); any element beating second proves a triplet β O(n) time, O(1) space.
Approach 1 β Brute force: three nested loops
Try every ordered triple of indices.
from typing import List
class Solution:
def increasingTriplet(self, nums: List[int]) -> bool:
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
if nums[i] < nums[j] < nums[k]:
return True
return False
Complexity: O(n^3) time, O(1) space. At n = 5 * 10^5 this is astronomically slow.
Approach 2 β Precompute left-min and right-max (DP-style baseline)
The insight: index j is the middle of a triplet iff some element to its left is smaller and some element to its right is larger. Precompute, for each position, the minimum to the left and the maximum to the right; then one scan checks the middle condition.
from typing import List
class Solution:
def increasingTriplet(self, nums: List[int]) -> bool:
n = len(nums)
if n < 3:
return False
left_min = [0] * n
right_max = [0] * n
left_min[0] = nums[0]
for i in range(1, n):
left_min[i] = min(left_min[i - 1], nums[i])
right_max[n - 1] = nums[n - 1]
for i in range(n - 2, -1, -1):
right_max[i] = max(right_max[i + 1], nums[i])
for j in range(1, n - 1):
if left_min[j - 1] < nums[j] < right_max[j + 1]:
return True
return False
Complexity: O(n) time but O(n) space for the two auxiliary arrays. This is the clear, easy-to-prove version β good to know, but it fails the O(1)-space follow-up.
Approach 3 β Two running minima (the greedy)
The greedy-choice property (why the local update is globally correct): Scan left to right holding two values, first (smallest seen so far) and second (smallest value for which a strictly smaller element appeared before it). Each new number x:
- if
x <= first, lower first β a smaller candidate small can only help a future triplet, and lowering first never invalidates an already-recorded second;
- else if
x <= second, lower second β we now have a valid (some earlier first) < x pair, and we keep the pairβs top as small as possible so more values can beat it later;
- else
x > second, and a triplet exists.
The subtle point: after we later lower first to a value positioned after the element that set second, it can look like first and second are out of order. That is fine β the guarantee we rely on is only that at the moment second was assigned, a genuinely smaller element preceded it. That historical fact never expires, so the instant any x exceeds second, a real i < j < k chain is certified. Greedily keeping both bounds as small as possible maximizes the chance a future element clears second, and it never produces a false positive.
from typing import List
class Solution:
def increasingTriplet(self, nums: List[int]) -> bool:
first = float('inf')
second = float('inf')
for x in nums:
if x <= first:
first = x
elif x <= second:
second = x
else:
return True
return False
Walkthrough on nums = [2,1,5,0,4,6]:
x=2: 2 <= inf β first=2. (first=2, second=inf)
x=1: 1 <= 2 β first=1. (first=1, second=inf)
x=5: 5 > 1 and 5 <= inf β second=5. (first=1, second=5)
x=0: 0 <= 1 β first=0. (first=0, second=5 β now βout of order,β but second=5 still remembers a smaller-before-it existed.)
x=4: 4 > 0 and 4 <= 5 β second=4. (first=0, second=4)
x=6: 6 > 0 and 6 > 4 β return True. β
(the certified triplet is 1 < 4 < 6.)
Complexity: O(n) time, O(1) space. Meets the follow-up.
Common pitfalls
- Using
< instead of <= in the comparisons. With <, duplicate values like [1,1,1,1] or ties can be mishandled; <= correctly keeps equal values from spuriously advancing to second.
- Being spooked by
first ending up positioned after the element that set second. It does not cause false positives β the proof relies only on the history at assignment time.
- Overflow worries: use
float('inf') sentinels (or None checks) so the initial comparisons behave.
Pattern takeaway
To detect a length-L increasing subsequence cheaply, greedily maintain the smallest possible βtailβ for each achievable length (here first and second). Keeping every bound minimal maximizes future extendability β the same idea generalizes to the patience-sorting O(n log n) longest-increasing-subsequence algorithm.