Overview
The longest increasing subsequence (LIS) is the longest subsequence whose values strictly increase. For [10, 9, 2, 5, 3, 7, 101, 18] the length is 4 (for example 2, 3, 7, 18). The simple DP computes dp[i] = length of the LIS ending at index i by checking all earlier j with a smaller value: O(n^2).
The O(n log n) solution keeps an array tails where tails[k] is the smallest possible ending value of an increasing subsequence of length k + 1. For each number, binary search the first tail >= it and replace that tail (or append). The length of tails is the LIS length. This 'patience sorting' idea is a classic interview upgrade.
Deal cards one by one onto piles. Each card goes on the leftmost pile whose top card is greater than or equal to it; if none, start a new pile. The number of piles at the end equals the length of the longest increasing subsequence.
When to use it
- Longest chain where each element must be bigger than the previous.
- Nesting problems after sorting: envelopes, boxes, pairs.
- Minimum deletions to make an array sorted (n - LIS).
- Counting how many longest increasing subsequences exist.
Problem patterns it solves
Recognize it when: longest strictly increasing subsequence.
- 300. Longest Increasing Subsequence
- 674. Longest Continuous Increasing Subsequence (contiguous variant)
Recognize it when: envelopes or boxes that nest in both dimensions.
- 354. Russian Doll Envelopes
- 1626. Best Team With No Conflicts
- 1691. Maximum Height by Stacking Cuboids
Recognize it when: pairs where one must follow another.
- 646. Maximum Length of Pair Chain
- 1048. Longest String Chain
Recognize it when: number of longest increasing subsequences.
- 673. Number of Longest Increasing Subsequence
Recognize it when: longest chain under a relation other than <.
- 368. Largest Divisible Subset
- 1218. Longest Arithmetic Subsequence of Given Difference
Recognize it when: minimum deletions to make increasing or mountain-shaped.
- 1671. Minimum Number of Removals to Make Mountain Array
- 2111. Minimum Operations to Make the Array K-Increasing
Where it is used in real software
Patience diff (used by git diff --patience) matches unique lines and computes an LIS to align files cleanly.
Finding the longest run of increasing prices or metrics, allowing gaps.
Stacking or nesting items such as containers or tasks with increasing requirements.
Key terms
- dp[i]
- Length of the LIS that ends exactly at index i.
- tails[k]
- Smallest tail value of any increasing subsequence of length k + 1.
- Lower bound
- First index in tails with value >= x (strictly increasing LIS).
- Strict vs non-decreasing
- Use lower bound for strict, upper bound for non-decreasing.
How it works, step by step
- 1O(n^2): initialize dp[i] = 1
Every element alone is an increasing subsequence.
- 2O(n^2): extend from smaller earlier values
For j < i with nums[j] < nums[i], dp[i] = max(dp[i], dp[j] + 1).
- 3O(n log n): binary search tails
For each x, find the first tail >= x.
- 4Replace or append
If found, replace it with x (a smaller tail is better for the future); otherwise append x.
- 5Answer
tails.length. Note tails itself is not necessarily a valid subsequence.
STEP 110: tails is empty, append. tails = [10].
O(n^2) dp table for the same input
nums = [10, 9, 2, 5, 3, 7, 101, 18]
| i | nums[i] | Smaller earlier values | dp[i] |
|---|---|---|---|
| 0 | 10 | - | 1 |
| 1 | 9 | - | 1 |
| 2 | 2 | - | 1 |
| 3 | 5 | 2 (dp 1) | 2 |
| 4 | 3 | 2 (dp 1) | 2 |
| 5 | 7 | 2, 5, 3 (best dp 2) | 3 |
| 6 | 101 | all earlier (best dp 3) | 4 |
| 7 | 18 | 10, 9, 2, 5, 3, 7 (best dp 3) | 4 |
NOWi: 0 | nums[i]: 10 | Smaller earlier values: - | dp[i]: 1
max(dp) = 4. The O(n^2) version is easier to extend (counting LIS, reconstruction); the tails version is faster for n up to 10^5.
Implementation
function lengthOfLIS(nums) { const tails = []; for (const x of nums) { let lo = 0, hi = tails.length; while (lo < hi) { // lower bound: first tail >= x const mid = (lo + hi) >> 1; if (tails[mid] < x) lo = mid + 1; else hi = mid; } tails[lo] = x; // replace or append (lo === tails.length) } return tails.length;} // O(n^2) with reconstructionfunction lisSequence(nums) { const n = nums.length; const dp = new Array(n).fill(1), prev = new Array(n).fill(-1); let bestEnd = 0; for (let i = 0; i < n; i++) { for (let j = 0; j < i; j++) { if (nums[j] < nums[i] && dp[j] + 1 > dp[i]) { dp[i] = dp[j] + 1; prev[i] = j; } } if (dp[i] > dp[bestEnd]) bestEnd = i; } const seq = []; for (let i = bestEnd; i !== -1; i = prev[i]) seq.push(nums[i]); return seq.reverse();} // 354. Russian Doll Envelopes: sort width asc, height desc, then LIS on heightsfunction maxEnvelopes(envelopes) { envelopes.sort((a, b) => a[0] - b[0] || b[1] - a[1]); return lengthOfLIS(envelopes.map((e) => e[1]));}Complexity and performance
Check all earlier elements.
One binary search per element.
dp or tails array.
Trade-offs
The quadratic version supports counting and custom relations easily. The tails version is much faster but only gives the length (reconstruction needs extra index tracking).
In Russian Doll Envelopes, sorting equal widths by descending height prevents two envelopes of the same width from both being chosen.
Variants and related techniques
Use upper bound (first tail > x) instead of lower bound.
LIS from the left plus LIS from the right minus one at each peak.
When the relation includes a gap limit (LIS II), query the best dp over a value range.
Common mistakes
- Returning tails as the actual LIS.
Fix: tails holds the smallest endings per length and may not be a real subsequence.
- Wrong bound for strict vs non-strict.
Fix: Strict: first >= x. Non-decreasing: first > x.
- Sorting envelopes by height ascending on ties.
Fix: Sort ties by height descending so same-width envelopes cannot chain.
Interview questions
Why is replacing a tail with a smaller value safe?
A subsequence of the same length ending with a smaller value can be extended by at least every value the larger ending could, so keeping the smallest ending never loses options.
What is the answer to 'minimum deletions to make the array strictly increasing'?
n minus the LIS length: keep the longest increasing subsequence and delete everything else.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 300. Longest Increasing Subsequence | Medium | Both methods. |
| 646. Maximum Length of Pair Chain | Medium | Sort plus LIS or greedy. |
| 368. Largest Divisible Subset | Medium | Custom relation with reconstruction. |
| 673. Number of Longest Increasing Subsequence | Medium | Counting. |
| 1048. Longest String Chain | Medium | Sort by length, DP by predecessor. |
| 354. Russian Doll Envelopes | Hard | 2D sort trick. |