title: 132 Pattern
slug: 132-pattern
difficulty: medium
leetcode_id: 456
leetcode_url: https://leetcode.com/problems/132-pattern/
categories:
- arrays
- stack
patterns:
- slug: monotonic-stack
is_optimal: true
function_signature: "def find132pattern(nums: list[int]) -> bool:"
test_cases:
visible:
- input: { nums: [1, 2, 3, 4] }
expected: false
- input: { nums: [3, 1, 4, 2] }
expected: true
- input: { nums: [-1, 3, 2, 0] }
expected: true
hidden:
- input: { nums: [1, 2] }
expected: false
- input: { nums: [3, 5, 0, 3, 4] }
expected: true
- input: { nums: [1, 0, 1, -4, -3] }
expected: false
- input: { nums: [1, 2, 2, 2] }
expected: false
- input: { nums: [-2, 1, 2, -2, 1, 2] }
expected: true
- input: { nums: [1, 3, 2, 4, 5, 6, 7, 8, 9, 10] }
expected: true
description: |
Given an array of `n` integers `nums`, a **132 pattern** is a subsequence of three integers `nums[i]`, `nums[j]` and `nums[k]` such that `i < j < k` and `nums[i] < nums[k] < nums[j]`.
Return `true` *if there is a **132 pattern** in* `nums`, *otherwise, return* `false`.
The "132" name comes from the value ordering: the 1st element is smallest, the 3rd element (middle index) is largest, and the 2nd element (last index) is in between.
constraints: |
- `n == nums.length`
- `1 <= n <= 2 * 10^5`
- `-10^9 <= nums[i] <= 10^9`
examples:
- input: "nums = [1,2,3,4]"
output: "false"
explanation: "There is no 132 pattern in the sequence. The array is strictly increasing, so we can never find nums[k] < nums[j] where k > j."
- input: "nums = [3,1,4,2]"
output: "true"
explanation: "There is a 132 pattern in the sequence: [1, 4, 2]. Here i=1, j=2, k=3, and nums[1]=1 < nums[3]=2 < nums[2]=4."
- input: "nums = [-1,3,2,0]"
output: "true"
explanation: "There are three 132 patterns in the sequence: [-1, 3, 2], [-1, 3, 0] and [-1, 2, 0]."
explanation:
intuition: |
Imagine you're looking for a specific "shape" in a sequence of numbers: a valley followed by a peak, with something in between on the right side.
The pattern we seek is **"small, big, medium"** in terms of values, appearing at indices **i < j < k**. Think of it like finding a mountain range where:
- Position `i` is in a valley (smallest value)
- Position `j` is at the peak (largest value)
- Position `k` is partway up the mountain (between the two)
The key insight is that if we scan from **right to left**, we can maintain candidates for the "2" (middle value, `nums[k]`) using a **monotonic stack**. As we pop elements from the stack, we're finding elements that could serve as valid `nums[k]` values — elements that are smaller than some `nums[j]` to their left.
By tracking the largest popped value (our best candidate for `nums[k]`), we just need to find any `nums[i]` to the left that's smaller than this candidate. If we find one, we've completed the 132 pattern!
approach: |
We solve this using a **Monotonic Stack (Right to Left)** approach:
**Step 1: Initialise tracking variables**
- `stack`: A monotonic decreasing stack to track potential `nums[j]` candidates
- `third`: The best candidate for `nums[k]` (the "2" in 132), initialised to negative infinity
**Step 2: Iterate from right to left**
- For each element `num` at position `i`, we check if it could be `nums[i]` (the "1" in 132)
- If `num < third`, we found a valid pattern! The current `num` is smaller than our `third` candidate, and `third` came from being popped by some larger element (which serves as `nums[j]`)
- Otherwise, maintain the monotonic stack:
- While `num` is greater than the stack top, pop elements
- Each popped element becomes a candidate for `third` (keep the maximum)
- Push `num` onto the stack
**Step 3: Return the result**
- If we found `num < third` at any point, return `true`
- If we finish the loop without finding a pattern, return `false`
Why does this work? When we pop an element from the stack, it means we found a larger element to its left (the current `num`). This popped element becomes a valid `third` because there exists some `nums[j] > nums[k]` with `j < k`. We then just need `nums[i] < nums[k]` with `i < j`.
common_pitfalls:
- title: The Brute Force Trap
description: |
A natural first approach is three nested loops checking all combinations of `i < j < k`:
```python
for i in range(n):
for j in range(i+1, n):
for k in range(j+1, n):
if nums[i] < nums[k] < nums[j]:
return True
```
This is **O(n³)** and with `n <= 2 * 10^5`, that's up to 8 × 10^15 operations — guaranteed **Time Limit Exceeded**.
wrong_approach: "Triple nested loops checking all triplets"
correct_approach: "Monotonic stack achieving O(n) time"
- title: Confusing the Index Order vs Value Order
description: |
The "132" refers to the **value ordering**, not the index ordering:
- Index order: `i < j < k` (always left to right)
- Value order: `nums[i] < nums[k] < nums[j]` (small, medium, big → positions 1, 3, 2)
A common mistake is looking for values in ascending order `nums[i] < nums[j] < nums[k]`, which would be a "123" pattern instead.
wrong_approach: "Looking for strictly increasing subsequence"
correct_approach: "Looking for small-big-medium pattern at indices i < j < k"
- title: Forgetting to Track the Maximum Popped Element
description: |
When using the monotonic stack approach, some implementations only check the most recently popped element. However, we need to track the **maximum** of all popped elements.
Consider `[1, 0, 1, -4, -3]`: we need to ensure our `third` candidate is the best possible one to maximise our chances of finding a valid `nums[i]`.
wrong_approach: "Only using the last popped element"
correct_approach: "Tracking the maximum of all popped elements as third"
- title: Handling Duplicate Values
description: |
The pattern requires **strict inequalities**: `nums[i] < nums[k] < nums[j]`. Equal values don't count.
For `[1, 2, 2, 2]`, there's no valid 132 pattern because we can't have `nums[k] < nums[j]` when they're equal.
wrong_approach: "Using <= comparisons"
correct_approach: "Using strict < comparisons throughout"
key_takeaways:
- "**Monotonic stack mastery**: This problem demonstrates how a monotonic stack can track relationships between elements in O(n) time"
- "**Right-to-left traversal**: Sometimes scanning backwards simplifies the logic — here it lets us build up `third` candidates before we need them"
- "**The popped element insight**: Elements popped from a monotonic stack have a guaranteed relationship with the element that caused the pop"
- "**Pattern recognition**: The 132 pattern appears in many variations (stock prices, sequences) — recognising this structure is valuable"
time_complexity: "O(n). Each element is pushed and popped from the stack at most once, giving us linear time."
space_complexity: "O(n). In the worst case (strictly decreasing array), the stack holds all n elements."
solutions:
- approach_name: Monotonic Stack (Right to Left)
is_optimal: true
code: |
def find132pattern(nums: list[int]) -> bool:
n = len(nums)
if n < 3:
return False
stack = [] # Monotonic decreasing stack for nums[j] candidates
third = float('-inf') # Best candidate for nums[k] (the "2" in 132)
# Scan from right to left
for i in range(n - 1, -1, -1):
# If current num is less than third, we found the pattern!
# Current num is nums[i], third is nums[k], and nums[j] exists
# (it's the element that popped third from the stack)
if nums[i] < third:
return True
# Maintain monotonic decreasing stack
# Pop smaller elements — they become candidates for third
while stack and nums[i] > stack[-1]:
third = stack.pop() # Update third to the largest popped value
# Push current element as potential nums[j]
stack.append(nums[i])
return False
explanation: |
**Time Complexity:** O(n) — Each element is pushed and popped at most once.
**Space Complexity:** O(n) — Stack can hold up to n elements.
The key insight is that when we pop elements from the stack, those elements are smaller than the current element (potential `nums[j]`), making them valid candidates for `nums[k]`. We track the largest such candidate in `third`. Then any element smaller than `third` that we encounter later completes the pattern.
- approach_name: Prefix Minimum with Stack
is_optimal: true
code: |
def find132pattern(nums: list[int]) -> bool:
n = len(nums)
if n < 3:
return False
# Precompute minimum from the left for each position
min_left = [0] * n
min_left[0] = nums[0]
for i in range(1, n):
min_left[i] = min(min_left[i - 1], nums[i])
# Use stack to find valid j, k pairs where min_left[j] < nums[k] < nums[j]
stack = [] # Stack of indices for potential nums[k]
# Scan from right to left
for j in range(n - 1, -1, -1):
# We need nums[i] < nums[k] < nums[j]
# min_left[j] gives us the minimum nums[i] to the left of j
if nums[j] > min_left[j]:
# Pop elements that are too small to be nums[k]
while stack and nums[stack[-1]] <= min_left[j]:
stack.pop()
# Check if top of stack is a valid nums[k]
if stack and nums[stack[-1]] < nums[j]:
return True
stack.append(j)
return False
explanation: |
**Time Complexity:** O(n) — Two passes: one for prefix minimum, one with stack.
**Space Complexity:** O(n) — For the prefix minimum array and stack.
This approach explicitly tracks the minimum to the left of each position. For each potential `nums[j]`, we know exactly what `nums[i]` would need to beat (`min_left[j]`). The stack maintains candidates for `nums[k]` that satisfy the constraints.
- approach_name: Brute Force
is_optimal: false
code: |
def find132pattern(nums: list[int]) -> bool:
n = len(nums)
for j in range(1, n - 1):
# Find minimum to the left of j
min_i = min(nums[:j])
# Check if any element to the right satisfies the pattern
for k in range(j + 1, n):
if min_i < nums[k] < nums[j]:
return True
return False
explanation: |
**Time Complexity:** O(n²) — For each j, we scan left for min and right for valid k.
**Space Complexity:** O(1) — No additional data structures.
This optimised brute force fixes `j` as the middle element, finds the minimum to its left, then searches for a valid `nums[k]` to its right. While better than O(n³), it's still too slow for large inputs and will TLE on LeetCode. Included to show the progression toward the optimal solution.