Skip to main content

Longest Increasing Subsequence: O(n^2) and O(n log n) Patience

What This Concept Is​

A strictly increasing subsequence preserves input index order and has strictly increasing values. It need not be contiguous. For nonempty input define L(i) as the longest such subsequence ending at i:

L(i)=1+max({L(j): j<i and a[j]<a[i]} union {0}).

The last predecessor partitions the possibilities. O(n²) comparisons compute all states; predecessors recover a witness. Empty input has length 0, avoiding an undefined maximum over no endpoints.

The faster algorithm stores tails[k]: the smallest endpoint value achievable by a subsequence of length k+1 in the processed prefix. For a new x, find the first tail at least x using lower-bound search, then replace it or append x.

Why It Matters Here​

This is a dominance reduction. For the same length, a smaller ending value can be extended by every future value that could extend a larger ending value. The discarded state cannot improve the optimal length later. Explain this dominance before claiming a smaller array is sufficient. Witness reconstruction needs information beyond the minimum tail values themselves.

Concrete Examples​

For [3,5,6,2,4], tails evolve [3], [3,5], [3,5,6], [2,5,6], [2,4,6]. Length is 3 and indices [0,1,2] provide [3,5,6]. The final tails array is not a valid subsequence: its 6 occurs before 2 and 4.

Why the update works. If x replaces position k, the preceding tail is less than x, so an existing length-k sequence can extend to length k+1. Positions before k already have smaller tails. Positions after k require longer sequences; x cannot create one because no length-(k+1) tail is less than x. Replacing with a smaller/equal endpoint preserves possible future extensions. These facts also keep tails strictly increasing, licensing binary search.

Duplicates. Input [2,2,2] has strict LIS length 1. First-tail->=x replaces position 0 each time. Searching first-tail->x instead computes a longest nondecreasing subsequence, whose length here is 3. This is a semantic change, not a harmless tie policy.

For reconstruction, keep the input index at each tail position and set prev[i] to the endpoint index at position k-1 before the replacement. Follow predecessors from the final longest endpoint and reverse the indices. All predecessors have smaller indices, so the chain terminates and respects order.

Common Confusion / Misconceptions​

Sorting the input destroys the original order constraint. A subsequence is not a substring. Patience-pile descriptions must specify equality behavior: for strict LIS, place on the first pile with top at least the new value. O(n log(n+1)) is the total comparison bound; each binary search takes O(log(n+1)), not O(n log n) per element. Reconstruction adds O(n) storage and O(n) worst-case work.

How To Use It​

Implement the quadratic recurrence first as a learning exercise, then justify tails dominance. In the lab, return original indices. Check increasing indices, strict values and length equality separately. Enumerate all subsequences on tiny inputs; include duplicates, decreasing input, negative values and empty input. Do not require the reference's particular tied subsequence.

Check Yourself​

Why can replacing a tail help without increasing the current length? Why are tails not necessarily a witness? What would change for nondecreasing output?

Mini Drill or Application​

For [2,2,1,3], give strict and nondecreasing answers with indices. Feedback: strict length 2, for example [0,3]; nondecreasing length 3, [0,1,3]. State the exact inequality used by both search variants.

Read This Only If Stuck​

Skiena: longest increasing sequence. Reconstruct the quadratic proof before using the faster implementation in Lab A.