3.7.11 · D4Algorithm Paradigms

Exercises — DP problems — Longest Increasing Subsequence (LIS) — O(n²) and O(n log n)

2,379 words11 min readBack to topic

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] — positions 0,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] — positions 0,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.

Figure — DP problems — Longest Increasing Subsequence (LIS) — O(n²) and O(n log n)

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 on B.
  • Or reverse A and 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]: set dp[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]. ✅

Figure — DP problems — Longest Increasing Subsequence (LIS) — O(n²) and O(n log n)

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 run 1,2,3 — take the first 1 (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_bound in 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.

Recap map

min deletions

negate values

sort width asc height desc

L1 recognize strict increasing

L2 run both engines

L3 transform decreasing and cost

L4 count LIS and min deletions

L5 2D envelopes and LCS bridge

n minus LIS

decreasing to increasing

plain LIS on heights