Find the longest subsequence (not necessarily contiguous) that is strictly increasing.

DP

vector<int> dp(n, 1);
for (int i = 0; i < n; i++)
    for (int j = 0; j < i; j++)
        if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1);
int ans = *max_element(dp.begin(), dp.end());

Simple, easy to modify, and enough for .

— the patience trick

Keep tail[k] = the smallest possible tail of an increasing subsequence of length . This array is always sorted, so each element is placed with a binary search.

int lis(vector<int>& a) {
    vector<int> tail;
    for (int x : a) {
        auto it = lower_bound(tail.begin(), tail.end(), x);   // strictly increasing
        if (it == tail.end()) tail.push_back(x);
        else *it = x;
    }
    return tail.size();
}

tail is not an LIS

The array holds the best tails per length, not an actual subsequence. To recover a real LIS, record for each element the length at which it was placed and its predecessor, then walk back.

vector<int> lisReconstruct(vector<int>& a) {
    int n = a.size();
    vector<int> tail, tailIdx, par(n, -1);
    for (int i = 0; i < n; i++) {
        auto it = lower_bound(tail.begin(), tail.end(), a[i]);
        int pos = it - tail.begin();
        if (pos > 0) par[i] = tailIdx[pos - 1];
        if (it == tail.end()) { tail.push_back(a[i]); tailIdx.push_back(i); }
        else { *it = a[i]; tailIdx[pos] = i; }
    }
    vector<int> res;
    for (int i = tailIdx.back(); i != -1; i = par[i]) res.push_back(a[i]);
    reverse(res.begin(), res.end());
    return res;
}

Strict vs non-strict

WantUse
Strictly increasinglower_bound
Non-decreasingupper_bound
Strictly decreasingreverse the array, or negate values, then strict increasing
Non-increasingreverse + non-decreasing

Getting this wrong is a one-character bug that produces plausible wrong answers.

Variants

VariantApproach
Count the number of LISkeep (length, count) per value in a segment tree over compressed values, or a second DP array
Longest bitonic subsequenceLIS from the left + LIS from the right, combine at each index
LIS with a bounded gap ()segment-tree max query over a value range
Longest chain of pairs / boxessort by one dimension (ties descending), LIS on the other
3D version (boxes in boxes)sort by one, then a 2D dominance DP with a BIT,
Minimum deletions to sort
Minimum non-increasing subsequences covering the array (Dilworth)
LIS of a permutation, huge the method is already optimal
LCS of two permutationsmap to indices, then LIS — instead of

That last one is worth remembering: the LCS of two permutations of the same set is exactly an LIS problem, which turns a quadratic algorithm into an one.

Dilworth’s theorem

The minimum number of non-increasing subsequences needed to cover a sequence equals the length of its longest increasing subsequence.

Equivalently: the minimum number of increasing subsequences covering the array equals the longest non-increasing subsequence. This converts several “partition into monotone pieces” problems into a single LIS computation.

Patience sorting

The algorithm is literally the card game patience: deal cards into piles, placing each on the leftmost pile whose top is the card. The number of piles is the LIS length. It is a good mental model for why the greedy tail replacement is correct — replacing a tail never shortens any achievable length and can only help future elements.

See also: LCS · Binary Search · Segment Tree