DYNAMIC PROGRAMMING / ALGORITHM BRIEF

Longest increasing subsequence

The longest increasing subsequence (LIS) is the longest subsequence whose values strictly increase.

IntermediatePhase 07 / Topic 6 of 9Mental modelComplexityEdge cases
01

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.

Patience card game

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.

02

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.
03

Problem patterns it solves

Classic LIS

Recognize it when: longest strictly increasing subsequence.

  • 300. Longest Increasing Subsequence
  • 674. Longest Continuous Increasing Subsequence (contiguous variant)
Sort then LIS (2D nesting)

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
Chains of pairs

Recognize it when: pairs where one must follow another.

  • 646. Maximum Length of Pair Chain
  • 1048. Longest String Chain
Counting LIS

Recognize it when: number of longest increasing subsequences.

  • 673. Number of Longest Increasing Subsequence
Divisible or custom relation

Recognize it when: longest chain under a relation other than <.

  • 368. Largest Divisible Subset
  • 1218. Longest Arithmetic Subsequence of Given Difference
Removals to be sorted

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
04

Where it is used in real software

Version and diff algorithms

Patience diff (used by git diff --patience) matches unique lines and computes an LIS to align files cleanly.

Stock and time-series analysis

Finding the longest run of increasing prices or metrics, allowing gaps.

Scheduling

Stacking or nesting items such as containers or tasks with increasing requirements.

05

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.
06

How it works, step by step

  1. 1
    O(n^2): initialize dp[i] = 1

    Every element alone is an increasing subsequence.

  2. 2
    O(n^2): extend from smaller earlier values

    For j < i with nums[j] < nums[i], dp[i] = max(dp[i], dp[j] + 1).

  3. 3
    O(n log n): binary search tails

    For each x, find the first tail >= x.

  4. 4
    Replace or append

    If found, replace it with x (a smaller tail is better for the future); otherwise append x.

  5. 5
    Answer

    tails.length. Note tails itself is not necessarily a valid subsequence.

tails[] for [10, 9, 2, 5, 3, 7, 101, 18]
Step 1 / 6
10
0

STEP 110: tails is empty, append. tails = [10].

07

O(n^2) dp table for the same input

nums = [10, 9, 2, 5, 3, 7, 101, 18]

Step 1 / 8
inums[i]Smaller earlier valuesdp[i]
010-1
19-1
22-1
352 (dp 1)2
432 (dp 1)2
572, 5, 3 (best dp 2)3
6101all earlier (best dp 3)4
71810, 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.

08

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]));}
09

Complexity and performance

Simple DPO(n^2)

Check all earlier elements.

Patience / binary searchO(n log n)

One binary search per element.

SpaceO(n)

dp or tails array.

10

Trade-offs

O(n^2) vs O(n log n)

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).

Sorting trick for 2D

In Russian Doll Envelopes, sorting equal widths by descending height prevents two envelopes of the same width from both being chosen.

11

Variants and related techniques

Non-decreasing LIS

Use upper bound (first tail > x) instead of lower bound.

Longest bitonic subsequence

LIS from the left plus LIS from the right minus one at each peak.

Segment tree / Fenwick LIS

When the relation includes a gap limit (LIS II), query the best dp over a value range.

12

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.

13

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.

14

Practice problems

ProblemDifficultyWhat it trains
300. Longest Increasing SubsequenceMediumBoth methods.
646. Maximum Length of Pair ChainMediumSort plus LIS or greedy.
368. Largest Divisible SubsetMediumCustom relation with reconstruction.
673. Number of Longest Increasing SubsequenceMediumCounting.
1048. Longest String ChainMediumSort by length, DP by predecessor.
354. Russian Doll EnvelopesHard2D sort trick.