Visual walkthrough — DP problems — Longest Increasing Subsequence (LIS) — O(n²) and O(n log n)
We build everything on one running array: Nothing else is assumed. Let us define our words before we use them.

Step 1 — Draw the array as heights, and draw one increasing chain
WHAT. Plot each value as a dot at horizontal position and height . An increasing subsequence is any set of dots you can connect with arrows that always go rightward and upward.
WHY. Height = value, position = index. "Increasing subsequence" now becomes a staircase that only ever climbs as you move right. That visual — a path that never steps down — is the whole problem. Everything below is about building the longest such staircase without ever looking at all possible paths.
PICTURE. Figure s01 above. The red arrows connect (length 4). Notice every arrow points up-and-to-the-right; that is the only rule.
Step 2 — The one bookkeeping array: tails
WHAT. We introduce a single array called tails. We read it like this:
Here starts at , so tails[0] is about length-1 chains, tails[1] about length-2 chains, and so on.
WHY the word "smallest". A chain of a given length is only useful for the future if it is easy to extend. To extend a chain by a new value , we need to be bigger than the chain's last value. So the smaller that last value (the "tail") is, the more future 's can attach. Among all chains of the same length, the one with the tiniest tail dominates every other — it accepts everything they accept, and more. That is why we only ever record the minimum tail per length.
PICTURE. Figure s02 shows two different length-2 chains ending in and in . The one ending in (red) is strictly better: any future value that could sit on top of can also sit on top of , but not vice-versa.

Step 3 — tails is automatically sorted (this is what lets us binary-search)
WHAT. Claim: tails is strictly increasing, i.e. — always, no matter what array we feed in.
WHY. Take any chain of length ending at value . Chop off its last element. You now have a chain of length whose tail is some value strictly less than (it came earlier in a rising chain). But is the smallest possible length- tail, so Chain the inequality: . Done — the array climbs.
PICTURE. Figure s03: a length-4 red chain; drop its top element and the remaining length-3 chain's tail is provably lower than the length-4 tail. That forces the staircase-of-tails to rise.

Step 4 — Processing one new value : find where it fits
WHAT. For each incoming value , we ask: what is the leftmost position where ? This is the operation called ==lower_bound== — "first slot not smaller than ".
WHY. Two things can happen and both are decided by that position:
- If such a exists, then can serve as a better (smaller) tail for length- chains, so we overwrite . We did not make any chain longer — we made an existing length cheaper to extend later.
- If no slot is (i.e. is bigger than every tail), then climbs on top of the currently-longest chain and we append it, growing the record length by one.
WHY and not . We want strictly increasing subsequences. Replacing the first tail that is equal to prevents a duplicate value from being treated as an extension (a tie is not an increase). If the problem allowed non-decreasing runs, we would search for the first tail strictly instead. This one comparison choice is the only difference between the two variants.
PICTURE. Figure s04: the sorted tails array as a number line; the red marker shows lower_bound landing on the first tail , with the "replace here" arrow.

Step 5 — Watch it run, frame by frame
WHAT. We push all of through the rule and record tails after each step.
WHY. Tracing turns the abstract rule into muscle memory, and it surfaces the two interesting moves — a replace that lowers a tail and an append that grows the length.
| leftmost tail ? | action | tails after |
|
|---|---|---|---|
| 3 | none | append | [3] |
| 1 | at | replace | [1] |
| 4 | none | append | [1,4] |
| 1 | at | replace | [1,4] |
| 5 | none | append | [1,4,5] |
| 9 | none | append | [1,4,5,9] |
| 2 | at | replace | [1,2,5,9] |
| 6 | at | replace | [1,2,5,6] |
Final length . ✓
PICTURE. Figure s05 overlays the growing tails staircase across these frames; the red segment is the value being replaced/appended at each moment.

Step 6 — Edge and degenerate cases (never leave the reader stranded)
WHAT + WHY + PICTURE, one row each. Figure s06 shows all four side by side.
- Empty array
[]: the loop never runs,tailsstays[], length . Correct — no elements, no subsequence. - Strictly decreasing
[5,4,3,2,1]: every is everything intails, so every step replaces position .tailsnever grows past length → . Correct: the longest rising chain is any single element. - Strictly increasing
[1,2,3,4,5]: nothing is ever that could block, every appends →tailsgrows every step → . Correct: the whole array already rises. - All equal
[7,7,7,7]: for strict LIS we use , so each finds the existing at and replaces it, never appending → . Correct: equal values are not an increase. (Under non-decreasing rules with , this would give — that is exactly why the vs choice matters.)

Recall Why does the decreasing case never append?
Append happens only when exceeds all current tails. In a decreasing array each new is smaller than everything seen, so lower_bound always lands at position and we replace — length stays .
The one-picture summary

This final figure fuses the whole derivation: the raw array as heights (top), the sorted tails staircase we maintain (middle, red = the smallest-tail invariant), and the single decision box (bottom) — lower_bound, then replace-or-append. The number of steps we ever append equals , and because tails is forced-sorted (Step 3) each decision costs only .
Recall Feynman: tell the whole walkthrough to a 12-year-old
Line up your cards left to right. You keep a row of piles. When a new card comes, walk your piles from the left and drop the card on the first pile whose top is not smaller than your card — you cover that top with your (smaller-or-equal) card. If no pile can take it (your card beats every top), start a brand-new pile on the right. Why cover with the smaller card? Because a smaller top is easier for future cards to beat, so more cards can pile up later. At the end, count your piles — that count is the length of the longest always-climbing line of cards hidden in your deck. The "first pile from the left" is just Binary Search on the always-sorted row of pile-tops, and that is the whole trick — the card game is Patience Sorting.
Connections
- Binary Search — the sorted-
tailsinvariant (Step 3) is what makeslower_boundlegal. - Patience Sorting — the card-pile picture in the Feynman retelling.
- Dynamic Programming — the cousin that this fast method optimizes.
- Greedy Algorithms — "keep smallest tails" is a greedy exchange argument, proven in Step 2.
- Longest Common Subsequence (LCS) — a related subsequence problem.
- Russian Doll Envelopes — a 2D application layered on top of LIS.