Quant Memo
Core

Longest Increasing Subsequence

The longest increasing subsequence problem has an obvious O(n^2) dynamic-programming solution and a much less obvious O(n log n) one built on binary search — the gap between them is a favorite interview escalation.

Prerequisites: Longest Common Subsequence

Given a sequence of daily prices, what is the longest stretch of days (not necessarily consecutive) where the price strictly increased each time you sampled it? That's the longest increasing subsequence (LIS) problem, and it's a good test of whether you can spot a hidden binary search inside a problem that looks like plain dynamic programming.

The idea: O(n²) DP, then a smarter O(n log n)

The direct DP: let dp[i]dp[i] be the length of the longest increasing subsequence ending exactly at index ii. For each ii, look back at every earlier index j<ij < i where nums[j]<nums[i]\text{nums}[j] < \text{nums}[i], and take the best chain that ends there, plus one:

dp[i]=1+maxj<i, nums[j]<nums[i]dp[j]dp[i] = 1 + \max_{j < i,\ \text{nums}[j] < \text{nums}[i]} dp[j]

with dp[i]=1dp[i] = 1 if no such jj exists (the element alone is a subsequence of length 1). The answer is maxidp[i]\max_i dp[i]. This is O(n2)O(n^2) — for each ii, scan all earlier jj.

The faster method keeps an auxiliary array tails, where tails[k] is the smallest possible tail value of any increasing subsequence of length k+1k+1 found so far. For each new number, binary-search tails for the leftmost position it can occupy (the first tail \ge the number) and overwrite that slot, or append if the number is larger than every tail. The length of tails at the end is the LIS length. This works because keeping the tail of each length as small as possible only ever helps future extensions — never hurts — so tails is always sorted and a binary search on it is always valid, giving O(nlogn)O(n \log n).

nums: 3, 1, 4, 1, 5, 9, 2, 6 31415926 tails after full pass: [1, 2, 5, 6] → LIS length 4 (e.g. 1,4,5,6) note: tails values need not be an actual subsequence, only its length is guaranteed correct
tails tracks the smallest tail achievable for each subsequence length; its final length is the LIS length, even though [1,2,5,6] itself may not appear verbatim in nums.

Worked example: [10, 9, 2, 5, 3, 7, 101, 18] with the fast method

Start tails = []. 10 → append, tails=[10]. 9 → binary search finds position 0 (first 9\ge 9), overwrite, tails=[9]. 2 → overwrite position 0, tails=[2]. 5 → larger than all, append, tails=[2,5]. 3 → binary search finds position 1 (first 3\ge 3 is 5), overwrite, tails=[2,3]. 7 → append, tails=[2,3,7]. 101 → append, tails=[2,3,7,101]. 18 → binary search finds position 3 (first 18\ge 18 is 101), overwrite, tails=[2,3,7,18]. Final length 4, matching the true LIS 2,3,7,18 (or 2,3,7,101).

import bisect
def lis_length(nums):                    # O(n log n)
    tails = []
    for x in nums:
        i = bisect.bisect_left(tails, x)
        if i == len(tails):
            tails.append(x)
        else:
            tails[i] = x
    return len(tails)

tails[k] is the smallest ending value achievable for an increasing subsequence of length k+1k+1 — not a real subsequence itself, just a bound that's always safe to binary search against. Overwriting a tail with a smaller value can only make future extensions easier, never invalid.

tails is not the actual LIS and often isn't even a valid subsequence of the input — interviewers routinely trip people up by asking to reconstruct the actual sequence, which needs a separate parent-pointer array, not a read of tails itself.

Where it shows up

Interview staples: LIS itself, Russian doll envelopes (2D LIS via sort-then-1D-LIS), and "maximum length of pair chain." On a desk, LIS-style reasoning identifies the longest stretch of consistently improving performance metrics in a noisy series, and the same binary-search-on-tails pattern shows up whenever you need the length of the longest monotonic run without recomputing from scratch on every new data point.

Related concepts

Practice in interviews

Further reading

  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (ch. 15, related)
ShareTwitterLinkedIn