Exercises — DP problems — Longest Increasing Subsequence (LIS) — O(n²) and O(n log n)
How to use this page: Cover the solution, try the problem, then reveal. Each level goes one rung harder — from recognizing what LIS is, to building new algorithms on top of it. This is the exercise companion to the parent LIS note.
Before we start, a one-screen refresher of the two tools you will keep reusing:
Level 1 — Recognition
L1.1 — Subsequence or not?
Given A = [3, 1, 4, 1, 5, 9, 2, 6]. For each candidate, say whether it is a valid increasing subsequence of A: (a) [3,4,5,9], (b) [1,4,5,2,6], (c) [3,4,5,6], (d) [1,1,5,9].
Recall Solution
A valid increasing subsequence must (1) appear in the same left-to-right order using original positions, and (2) be strictly increasing.
- (a)
[3,4,5,9]— positions0,2,4,5, values rise 3<4<5<9. ✅ - (b)
[1,4,5,2,6]— values are not increasing (5 then 2 goes down). ❌ - (c)
[3,4,5,6]— positions0,2,4,7, all in order, 3<4<5<6. ✅ - (d)
[1,1,5,9]— 1 is not < 1, equal values break strict increase. ❌
Valid: (a) and (c).
L1.2 — Read off dp values
For A = [2, 5, 3, 7], fill the table and give the answer.
Recall Solution
| i | A[i] | valid j (A[j]<A[i]) | dp[i] |
|---|---|---|---|
| 0 | 2 | none | 1 |
| 1 | 5 | j=0 (2<5), dp0=1 | 2 |
| 2 | 3 | j=0 (2<3), dp0=1 | 2 |
| 3 | 7 | j=0,1,2 → max dp = 2 | 3 |
dp = [1,2,2,3], answer = 3 (e.g. [2,5,7] or [2,3,7]).
Level 2 — Application
L2.1 — Full trace
Run the DP on A = [4, 10, 4, 3, 8, 9]. Give the whole dp array and one optimal subsequence.
Recall Solution
| i | A[i] | predecessors j with A[j]<A[i] (dp) | dp[i] |
|---|---|---|---|
| 0 | 4 | — | 1 |
| 1 | 10 | j=0 (4<10, dp 1) | 2 |
| 2 | 4 | none smaller (4 not <4) | 1 |
| 3 | 3 | none smaller | 1 |
| 4 | 8 | j=0 (4,dp1), j=2 (4,dp1), j=3 (3,dp1) → max 1 | 2 |
| 5 | 9 | j=0(4,1), j=2(4,1), j=3(3,1), j=4(8,dp2) → max 2 | 3 |
dp = [1,2,1,1,2,3], answer = 3. Trace back from dp[5]=3: 9 came from index 4 (8, dp 2), which came from index 0 or 2 or 3 (4 or 3, dp 1). One optimal: [4, 8, 9] (indices 0,4,5) or [3,8,9].
L2.2 — Full patience-sorting trace
Run patience sorting on A = [7, 2, 8, 1, 3, 4, 10, 6]. Show tails after each step and the final length.
Recall Solution
Use lower_bound (leftmost tails[k] >= x); replace, else append.
| x | search result | action | tails after |
|---|---|---|---|
| 7 | empty | append | [7] |
| 2 | tails[0]=7 ≥ 2 | replace[0] | [2] |
| 8 | none ≥ 8 | append | [2,8] |
| 1 | tails[0]=2 ≥ 1 | replace[0] | [1,8] |
| 3 | tails[1]=8 ≥ 3 | replace[1] | [1,3] |
| 4 | none ≥ 4 | append | [1,3,4] |
| 10 | none ≥ 10 | append | [1,3,4,10] |
| 6 | tails[3]=10 ≥ 6 | replace[3] | [1,3,4,6] |
Length = 4 (e.g. [2,3,4,10] or [1,3,4,6]). See the pile figure below.

Level 3 — Analysis
L3.1 — Longest decreasing subsequence
Given A = [5, 1, 6, 2, 7, 3], find the longest strictly decreasing subsequence length. Reuse an LIS engine — do not invent new code.
Recall Solution
Key idea: a decreasing subsequence of A is an increasing subsequence of the reversed comparison. Two clean tricks, both give the same length:
- Negate every value:
B = [-5,-1,-6,-2,-7,-3], then run LIS onB. - Or reverse
Aand run longest decreasing = LIS of reversed if you flip sign; simplest is the negation trick.
Run LIS (patience) on B = [-5,-1,-6,-2,-7,-3]:
| x | tails |
|---|---|
| -5 | [-5] |
| -1 | [-5,-1] |
| -6 | [-6,-1] |
| -2 | [-6,-2] |
| -7 | [-7,-2] |
| -3 | [-7,-3] |
Length = 2. Check on original: decreasing pairs like [5,1], [6,2], [7,3] — length 2, and no length-3 decreasing chain exists (the array keeps jumping back up). ✅
L3.2 — Why and not : count the work
For A of length , roughly how many primitive comparisons does each method do? Explain which term dominates.
Recall Solution
- : the inner loop over does about comparisons. Far too many for a second of runtime.
- : each of elements triggers one binary search over
tails(length ), costing comparisons. Here , so about comparisons. - Ratio faster. The term dominates and is why we replace the linear scan for a predecessor with a binary search.
Level 4 — Synthesis
L4.1 — Number of LIS (count, not just length)
For A = [1, 3, 5, 4, 7], find both the LIS length and how many distinct LIS achieve it.
Recall Solution
Extend the DP with a second array cnt[i] = number of LIS ending at i.
Rule: for each i, over all j<i with A[j]<A[i]:
- if
dp[j]+1 > dp[i]: setdp[i]=dp[j]+1,cnt[i]=cnt[j](found a strictly longer way — reset the count). - if
dp[j]+1 == dp[i]:cnt[i] += cnt[j](another way of the same best length).
| i | A[i] | dp[i] | cnt[i] | reasoning |
|---|---|---|---|---|
| 0 | 1 | 1 | 1 | alone |
| 1 | 3 | 2 | 1 | from 0 |
| 2 | 5 | 3 | 1 | from 1 |
| 3 | 4 | 3 | 1 | from 1 (3<4), dp 2+1=3, cnt=cnt[1]=1 |
| 4 | 7 | 4 | 2 | from 2 (dp3→4, cnt1) and from 3 (dp3→4, cnt1) → total 2 |
Length = 4, number of LIS = 2: [1,3,5,7] and [1,3,4,7]. ✅
L4.2 — Minimum deletions to sort
Given A = [4, 3, 6, 5, 8, 7], what is the fewest elements you must delete so the remaining array is strictly increasing?
Recall Solution
Reframe: whatever you keep must be a strictly increasing subsequence, so keeping the maximum means keeping an LIS. Minimum deletions .
Run LIS on A = [4,3,6,5,8,7]:
| x | tails |
|---|---|
| 4 | [4] |
| 3 | [3] |
| 6 | [3,6] |
| 5 | [3,5] |
| 8 | [3,5,8] |
| 7 | [3,5,7] |
LIS length = 3 (e.g. [3,5,7] or [4,6,8]). , so minimum deletions . ✅
Level 5 — Mastery
L5.1 — Russian Doll Envelopes (2D LIS)
Envelopes [[5,4],[6,4],[6,7],[2,3]] (width, height). One envelope fits inside another only if both width and height are strictly larger. Find the maximum number you can nest. (See Russian Doll Envelopes.)
Recall Solution
The trick: sort by width ascending, and for ties in width, sort height descending. Then run plain LIS on the heights.
- Why width asc: nesting requires increasing width, so process envelopes left to right in width order.
- Why height descending on equal widths: two envelopes of the same width can never nest (width not strictly larger). Sorting their heights descending guarantees at most one of them is picked by the height-LIS, because a descending run can't be an increasing subsequence.
Sort: widths 2,5,6,6. The two width-6 envelopes get heights 7,4 sorted descending:
[[2,3],[5,4],[6,7],[6,4]]. Heights sequence: [3, 4, 7, 4].
Run LIS on [3,4,7,4]:
| x | tails |
|---|---|
| 3 | [3] |
| 4 | [3,4] |
| 7 | [3,4,7] |
| 4 | replace tails[1]=4 with 4 → [3,4,7] |
LIS = 3. Answer = 3: [2,3] ⊂ [5,4] ⊂ [6,7]. ✅

L5.2 — LIS via LCS (cross-check)
The parent claims . Verify it on A = [3, 1, 2, 1, 3]. (See Longest Common Subsequence (LCS).)
Recall Solution
Why it works: an increasing subsequence of A is a subsequence that also appears, in the same order, inside the sorted list of A's distinct values. The longest common one to both is exactly the LIS. We use sorted-unique so equal values can't be matched twice (that keeps it strictly increasing).
A = [3,1,2,1,3],sorted-unique(A) = [1,2,3].- LCS of
[3,1,2,1,3]and[1,2,3]: the common increasing run1,2,3— take the first1(index 1),2(index 2),3(index 4). Length 3. - Direct LIS of
A:[1,2,3]→ length 3. ✅ They match.
Connections
- Dynamic Programming — L2, L4 are direct DP-table drills.
- Binary Search — the
lower_boundin every patience trace. - Patience Sorting — the pile model in L2.2 (figure).
- Longest Common Subsequence (LCS) — L5.2 reduction.
- Greedy Algorithms — "keep smallest tails" exchange argument behind L2/L3.
- Russian Doll Envelopes — L5.1 2D extension.