Skip to content

334. Increasing Triplet Subsequence (Medium)

Problem

Given an integer array nums, return true if there exist indices i < j < k such that nums[i] < nums[j] < nums[k]. Return false otherwise.

Examples

  • [1, 2, 3, 4, 5]true (triplet: 1, 2, 3)
  • [5, 4, 3, 2, 1]false (strictly decreasing, no triplet)
  • [2, 1, 5, 0, 4, 6]true (triplet: 1, 4, 6)

Constraints

  • 1n5×1051 \leq n \leq 5 \times 10^5
  • 231nums[i]2311-2^{31} \leq nums[i] \leq 2^{31} - 1

LeetCode 334 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force

Check every (i, j, k) combination with i < j < k.

def increasing_triplet(nums: list[int]) -> bool:
n = len(nums) # L1: O(1)
for i in range(n - 2): # L2: outer loop
for j in range(i + 1, n - 1): # L3: middle loop
for k in range(j + 1, n): # L4: inner loop
if nums[i] < nums[j] < nums[k]: # L5: check triplet
return True
return False

Where the time goes, line by line

Variables: n = len(nums).

LinePer-call costTimes executedContribution
L2 (outer loop)O(1)O(1)nnO(n)O(n)
L3 (middle loop)O(1)O(1)up to nn per iO(n2)O(n^2)
L4, L5 (inner loop)O(1)O(1)up to nn per jO(n3)O(n^3) ← dominates

Complexity

  • Time: O(n3)O(n^3), three nested loops.
  • Space: O(1)O(1), no extra storage.

Approach 2: Prefix min and suffix max

Build two auxiliary arrays: min_left[i] = minimum of nums[0..i], max_right[i] = maximum of nums[i..n-1]. For any candidate middle index j, a valid triplet exists when min_left[j-1] < nums[j] < max_right[j+1].

def increasing_triplet(nums: list[int]) -> bool:
n = len(nums)
if n < 3:
return False
min_left = [0] * n # L1: prefix-min array
max_right = [0] * n # L2: suffix-max array
min_left[0] = nums[0]
for i in range(1, n): # L3: build prefix mins
min_left[i] = min(min_left[i - 1], nums[i])
max_right[n - 1] = nums[n - 1]
for i in range(n - 2, -1, -1): # L4: build suffix maxes
max_right[i] = max(max_right[i + 1], nums[i])
for j in range(1, n - 1): # L5: scan for valid middle
if min_left[j - 1] < nums[j] < max_right[j + 1]: # L6: triplet found
return True
return False

Where the time goes, line by line

Variables: n = len(nums).

LinePer-call costTimes executedContribution
L1, L2 (init arrays)O(1)O(1)nnO(n)O(n)
L3 (prefix loop)O(1)O(1)nnO(n)O(n)
L4 (suffix loop)O(1)O(1)nnO(n)O(n)
L5, L6 (scan loop)O(1)O(1)nnO(n)O(n)

Three linear passes; constant factor is 3 with early exit.

Complexity

  • Time: O(n)O(n), three passes over the array.
  • Space: O(n)O(n), two auxiliary arrays of length nn.

Approach 3: Greedy two-variable scan

Track two thresholds: first = the smallest value seen so far, second = the smallest value that has a smaller value somewhere before it. If any later element exceeds second, a valid triplet is guaranteed.

def increasing_triplet(nums: list[int]) -> bool:
first = second = float('inf') # L1: two threshold sentinels
for n in nums: # L2: single pass
if n <= first: # L3: new global minimum
first = n
elif n <= second: # L4: new second-place minimum
second = n
else: # L5: found a third greater than both
return True
return False

The tricky invariant

first can be updated to an index that comes after second was set, which looks wrong. How can (first, second) be a valid ordered pair if first’s position is later?

The key: when second was set, some earlier value smaller than second existed at that moment. Even if first later drops lower, second still carries its guarantee — there is some value before it that was smaller. So when any later element exceeds second, a valid triplet is guaranteed to exist, even if first no longer points to the specific element that paired with second.

Example walkthrough: [5, 1, 4, 2, 3]

ValuefirstsecondAction
55infL3: update first
11infL3: update first (smaller)
414L4: update second
212L4: update second (smaller)
312L5: 3 > second=2, return True

The triplet is (1, 2, 3) at indices (1, 3, 4). first was updated after second was first set, but the invariant held throughout.

Where the time goes, line by line

Variables: n = len(nums).

LinePer-call costTimes executedContribution
L1 (init sentinels)O(1)O(1)1O(1)O(1)
L2-L5 (loop body)O(1)O(1)nnO(n)O(n) ← dominates

Complexity

  • Time: O(n)O(n), one pass.
  • Space: O(1)O(1), two scalar variables.

How to recognize this pattern

The trigger phrase: “return true if there exist indices i < j < k such that nums[i] < nums[j] < nums[k].” Fixed-length strictly increasing subsequence, existence question, no need to reconstruct it.

The tempting but incorrect approach: track all pairs (i, j) and scan for any k > j where nums[k] > nums[j]. That is O(n²) and misses the O(n) path entirely.

The greedy insight: you only need two thresholds, not all pairs. At every step, only two questions matter: “does a valid first exist?” and “given that first, does a valid second exist?” Each new element either lowers a threshold or, if it exceeds both, closes the case.

This generalizes weakly: for length-2 just track a running minimum. For length-4 and beyond, patience sort (LIS in O(n log n)) is needed. The two-variable trick is specific to length-3.

ProblemSame shape
300. Longest Increasing SubsequenceExistence + reconstruction, arbitrary length, patience sort
53. Maximum SubarrayGreedy running state, one pass
55. Jump GameGreedy running max, one pass

Key takeaways

  • first and second are thresholds, not index trackers. Once set, they mean “there exists some value this small with a valid prefix.”
  • Updating first after second is already set does not invalidate second’s guarantee.
  • The two-variable trick works only for length exactly 3. Use patience sort for arbitrary LIS length.
  • Edge cases: n < 3 is always false; all-equal arrays are false because the problem requires strict inequality.
  • Greedy Algorithms, the local choice pattern protected by an invariant about the best reachable future.
  • Array Scans, the linear pass habit of carrying just enough state while reading each item once.