2Brute2Best
🔥0

Longest Increasing Subsequence

Medium·LeetCode #300Basic Anchor
LeetCode
30-Second Mental ModelLongest Increasing Subsequence (classic: dp per end, or tails with binary search)
Complexity:O(2^N)→O(N log N)
Key Insight

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.

Core Validity Invariant

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.

01

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?

Input

An integer array nums.

Output

One number: the length of the longest strictly increasing subsequence.

Example 1
Input: nums = [10, 9, 2, 5, 3, 7, 101, 18]
Output: 4

One longest chain is 2, 3, 7, 101. Another is 2, 3, 7, 18. No chain has 5 numbers.

Example 2
Input: nums = [0, 1, 0, 3, 2, 3]
Output: 4

The chain 0, 1, 2, 3 uses the items at positions 0, 1, 4 and 5. It skips the other items.

Example 3
Input: nums = [7, 7, 7, 7, 7, 7, 7]
Output: 1

Equal numbers do not count as increasing, so every chain has one number.

Constraints
  • • 1 ≤ nums.length ≤ 2500
  • • −10^4 ≤ nums[i] ≤ 10^4
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    A subsequence can skip numbers. Does the chain have to sit next to itself in the list?

  2. 2.

    If I decide that the chain ENDS at this number, what must the number just before it in the chain look like?

  3. 3.

    Two numbers are equal. Can both be in a strictly increasing chain?

  4. 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. 5.

    Is the answer always the chain that ends at the last number? Try [3, 1, 2] and check.

03

Brute force approach

04

Better approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceRecurse over the numbers: skip one, or take it if it is bigger than the last one takenO(2^N)O(N)
Betterdp[i] = 1 + the best dp[j] over earlier smaller j; each dp[i] is filled onceO(N^2)O(N)
OptimalKeep the smallest possible end for each chain length; each number costs one binary searchO(N log N)O(N)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “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.
Subsequence (skip allowed)+each number extends a smaller earlier end+keep the best length per end, or the smallest end per length→Longest Increasing Subsequence (classic: dp per end, or tails with binary search)

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.

10

Don't make this mistake

Pattern confusion: a contiguous run instead of a subsequence
✗ int run = 1, best = 1; for (int i = 1; i < n; i++) { run = nums[i] > nums[i - 1] ? run + 1 : 1; best = max(best, run); }
✓ for (int j = 0; j < i; j++) if (nums[j] < nums[i]) dp[i] = max(dp[i], dp[j] + 1);

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 instead of lower_bound with equal values
✗ auto pos = upper_bound(tails.begin(), tails.end(), x);
✓ auto pos = lower_bound(tails.begin(), tails.end(), x);

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.

Using <= in the dp check
✗ if (nums[j] <= nums[i]) dp[i] = max(dp[i], dp[j] + 1);
✓ if (nums[j] < nums[i]) dp[i] = max(dp[i], dp[j] + 1);

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.

Treating tails as the chain itself
✗ // rebuild the answer from the tails array return tails;
✓ // tails only gives the length return tails.size();

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.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like DP — Longest Increasing Subsequence translate into production architectures and low-level memory mechanics.