Longest Increasing Subsequence
Keep an array tails. tails[k] is the smallest value that can end an increasing chain of length k + 1. For each number x, binary search for the first tail that is at least x. If there is none, append x, since it makes a longer chain. Otherwise replace that tail with x, which keeps the same length with a smaller end.
tails[k] is always the smallest possible end of a chain of length k + 1, so each new number can be placed with one binary search.
Problem
You are given a list of whole numbers nums.
A subsequence keeps the original order, but it may skip numbers. Find the length of the longest subsequence where every number is strictly bigger than the one before it.
Follow-up: can you do better than comparing each number with all the numbers before it?
An integer array nums.
One number: the length of the longest strictly increasing subsequence.
One longest chain is 2, 3, 7, 101. Another is 2, 3, 7, 18. No chain has 5 numbers.
The chain 0, 1, 2, 3 uses the items at positions 0, 1, 4 and 5. It skips the other items.
Equal numbers do not count as increasing, so every chain has one number.
- • 1 ≤ nums.length ≤ 2500
- • −10^4 ≤ nums[i] ≤ 10^4
First, think yourself
Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.
- 1.
A subsequence can skip numbers. Does the chain have to sit next to itself in the list?
- 2.
If I decide that the chain ENDS at this number, what must the number just before it in the chain look like?
- 3.
Two numbers are equal. Can both be in a strictly increasing chain?
- 4.
If I know the longest chain for every ending position so far, how could I get the answer for the next position from them?
- 5.
Is the answer always the chain that ends at the last number? Try [3, 1, 2] and check.
Brute force approach
Better approach
Optimized approach
Why does this algorithm work?
Dry run
All approaches compared
| Approach | Idea | Time | Space |
|---|---|---|---|
| Brute Force | Recurse over the numbers: skip one, or take it if it is bigger than the last one taken | O(2^N) | O(N) |
| Better | dp[i] = 1 + the best dp[j] over earlier smaller j; each dp[i] is filled once | O(N^2) | O(N) |
| Optimal | Keep the smallest possible end for each chain length; each number costs one binary search | O(N log N) | O(N) |
Why does this belong to this pattern?
- “subsequence”Numbers can be skipped, so the chain is any set of positions in order. It is not a window of neighbours.
- “strictly increasing”Each new number must be bigger than the last one in the chain. Equal numbers break the chain, so the check is <, not <=.
- “length of the longest”We want a length, so each end keeps its best length, and the answer is the best of all ends.
This is the anchor of the pattern. The key rule is that dp[i] only looks at earlier smaller items, so each end is solved from the ends before it. The answer is not always at the last index: the answer is the best length over all ends. The other classic questions in this subpattern change the validity rule (a fixed length, a sum, or a count), so this one is the base for all of them.
Don't make this mistake
The wrong version only looks at neighbours, so it is a longest increasing subarray. On [10, 9, 2, 5, 3, 7, 101, 18] it finds the run 3, 7, 101 and prints 3. The right answer is 4: the chain 2, 3, 7, 101 skips the 5 between 2 and 3.
upper_bound moves past an equal tail, so an equal number gets appended. On [1, 3, 3, 3] the wrong version builds tails = [1], [1, 3], [1, 3, 3], [1, 3, 3, 3] and prints 4. The right answer is 2 (1, 3), because a strictly increasing chain cannot use two 3s.
With <=, each 7 chains onto the 7 before it. On [7, 7, 7] the dp values become 1, 2, 3 and the answer is 3. The right answer is 1, because equal numbers are not strictly increasing.
The length of tails is right, but its values are not always a chain in the input order. On [2, 3, 1, 4], tails ends as [1, 3, 4]. Those three values are not in order in the list (1 comes after 3), so they are not a subsequence. The real longest chain is 2, 3, 4. To rebuild a real chain you need to keep a parent link for each number.
Want to implement this?
Write it yourself on LeetCode, then come back and compare your code with the solutions above.
Open coding workspaceHow this concept connects to System Design & C++ Systems
Top engineering interviews test whether you understand how algorithms like DP — Longest Increasing Subsequence translate into production architectures and low-level memory mechanics.
Foundations: Object-Oriented Principles in C++23
Class invariants, pure virtual interfaces, and RAII smart pointers.
Food Delivery System (Zomato Architecture)
Real-world machine coding application of algorithms and design patterns.
Pointers vs References Deep Dive
Pointer arithmetic and memory layout under the hood.
Virtual Method Table (vtable) & Memory Layout
Polymorphism, vptr overhead, and multiple inheritance in memory.