Coding Interview Patterns Module 24 – Dynamic programming: foundations
LIS applications: envelopes and chains
Russian doll envelopes as nesting gift boxes, and the longest chain of bookings: the problems with their limits, a brute force and what to notice.
What you will learn
- Transform nesting boxes in two dimensions into a longest increasing subsequence after one sort
- Prove that the sorted order loses no nest and admits no false one
- Find the longest chain of bookings with a greedy rule, and again with the tails method
- Count the longest increasing subsequences in O(n log n) with a Fenwick tree
- Extend the method to turned boxes, touching sides and boxes with three sides
Before you start
What you’ll learn · Before you start
What you will learn
- Transform nesting boxes in two dimensions into a longest increasing subsequence after one sort
- Prove that the sorted order loses no nest and admits no false one
- Find the longest chain of bookings with a greedy rule, and again with the tails method
- Count the longest increasing subsequences in O(n log n) with a Fenwick tree
- Extend the method to turned boxes, touching sides and boxes with three sides
Before you start
On this page
Several interview problems become a longest increasing subsequence (LIS) once the input is sorted the right way. The best known is nesting envelopes or boxes one inside another, often called Russian doll envelopes: sorted by width, a nest is a strictly rising run of heights, with one trap when widths are equal. A second is the longest chain of pairs, where each pair starts after the one before it ends, which a short greedy rule solves. A third counts how many longest runs there are, which matters when a leaderboard must say how many ways a best streak can happen.
Two problems: gift boxes and bookings
Problem A: nesting gift boxes. A shop keeps n flat gift boxes, box i of width w[i] and height h[i]. A box
fits inside another when it is strictly narrower and strictly lower, and boxes are never turned. Return the largest
number of boxes that can be nested one inside the next. Limits: 1 ≤ n ≤ 10⁵ and 1 ≤ w[i], h[i] ≤ 10⁹.
[(30, 20), (40, 25), (40, 35), (25, 30), (50, 45), (35, 25)]gives 4: (30, 20) inside (35, 25) inside (40, 35) inside (50, 45).[(10, 10), (10, 20), (10, 30)]gives 1, because boxes of equal width never nest.
Problem B: a chain of bookings. A hall gets n bookings, each a pair (start, end) of whole hours with start < end. A chain lists bookings so that each starts strictly after the one before it ends. Return the length of the longest chain. Limits: 1 ≤ n ≤ 10⁵ and 0 ≤ start < end ≤ 10⁹.
[(1, 4), (2, 3), (5, 8), (6, 7), (9, 10)]gives 3, for example 2 to 3, 6 to 7 and 9 to 10.[(3, 5), (1, 2), (2, 3)]gives 2, because a booking that starts in the hour another ends does not follow it.
The first example of each problem, with one answer drawn
Text description of the diagram
The upper panel shows problem A. Four boxes stand side by side, each a little wider and taller than the one before: 30 by 20, 35 by 25, 40 by 35 and 50 by 45. The word "in" between them reads as "fits inside": each box fits inside the next, so these four boxes form a nest of 4, the answer for the first example. The other two boxes of that example, 25 by 30 and 40 by 25, are not drawn.
The lower panel shows problem B on a time line from hour 0 to hour 11. The five bookings, 1 to 4, 2 to 3, 5 to 8, 6 to 7 and 9 to 10, are drawn as bars, one per row. Three of them are filled in: 2 to 3, 6 to 7 and 9 to 10. Each of those starts after the one before it ends, so they form a chain of 3, the answer for the second problem's first example. The other two bars overlap a chosen booking and stay grey.
The brute force, and why it fails at these sizes
For either problem, try every set of boxes or bookings and keep the largest set that sorts into a nest or a chain. The run below counts the sets: 256 for 8 boxes and 65,536 for 16, doubling with every box. At 100,000 boxes there are 2¹⁰⁰⁰⁰⁰ sets, a number with 30,103 digits. Comparing every pair of boxes instead still means about 5 × 10⁹ comparisons at that size, too many for a limit of a few seconds.
"""Both problems by brute force: try every set of boxes or bookings, and count the sets tried."""
import math
def fits(a, b):
"""Box a fits inside box b: strictly narrower and strictly lower (boxes are never turned)."""
return a[0] < b[0] and a[1] < b[1]
def most_nested(boxes):
best, tried = 0, 0
for mask in range(1 << len(boxes)):
tried += 1
chosen = sorted(b for i, b in enumerate(boxes) if mask >> i & 1)
if all(fits(x, y) for x, y in zip(chosen, chosen[1:])):
best = max(best, len(chosen))
return best, tried
def longest_chain(bookings):
best, tried = 0, 0
for mask in range(1 << len(bookings)):
tried += 1
chosen = sorted(p for i, p in enumerate(bookings) if mask >> i & 1)
if all(x[1] < y[0] for x, y in zip(chosen, chosen[1:])):
best = max(best, len(chosen))
return best, tried
for boxes in ([(30, 20), (40, 25), (40, 35), (25, 30), (50, 45), (35, 25)], [(10, 10), (10, 20), (10, 30)]):
best, tried = most_nested(boxes)
print(f"boxes {boxes}: {best} nested, {tried} sets tried")
for bookings in ([(1, 4), (2, 3), (5, 8), (6, 7), (9, 10)], [(3, 5), (1, 2), (2, 3)]):
best, tried = longest_chain(bookings)
print(f"bookings {bookings}: a chain of {best}, {tried} sets tried")
print()
for n in (8, 12, 16):
boxes = [(i, i) for i in range(n)] # every set nests, the worst case for checking
best, tried = most_nested(boxes)
print(f"{n} boxes: {tried:,} sets tried")
digits = math.floor(100_000 * math.log10(2)) + 1
print(f"100,000 boxes: 2 ** 100000 sets, a number with {digits:,} digits") Output
boxes [(30, 20), (40, 25), (40, 35), (25, 30), (50, 45), (35, 25)]: 4 nested, 64 sets tried boxes [(10, 10), (10, 20), (10, 30)]: 1 nested, 8 sets tried bookings [(1, 4), (2, 3), (5, 8), (6, 7), (9, 10)]: a chain of 3, 32 sets tried bookings [(3, 5), (1, 2), (2, 3)]: a chain of 2, 8 sets tried 8 boxes: 256 sets tried 12 boxes: 4,096 sets tried 16 boxes: 65,536 sets tried 100,000 boxes: 2 ** 100000 sets, a number with 30,103 digits
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
What to notice
- Sorted by width, a nest reads from left to right, and its heights rise strictly, so it is an increasing subsequence of the heights, as long as two boxes of the same width can never both be picked.
- A chain is ordered by its ends, and of all possible first bookings, the one that ends first leaves the most room for the rest.
Three methods follow. Sort-then-LIS orders the boxes by width and finds a longest strictly rising run of heights, with equal widths arranged so that they cannot chain. The greedy by end takes the booking that ends first, then each booking that starts after the last one taken ends. Counting keeps, beside each run length, how many runs reach it, and a Fenwick tree finds those counts quickly.
Boxes: sort by width, then a rising run of heights Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- A dry run on the six boxes
- Why equal widths take falling heights, with the failure reproduced
- Why the sorted order loses no nest and admits no false one: the proof
- Chains of bookings: the greedy by end, and the same answer from tails
- Why taking the earliest end first loses nothing
- The same answer with the tails method
- Two greedy rules that fail, reproduced
- Counting the longest runs on a leaderboard
- A table of lengths and counts, O(n²)
- A Fenwick tree over ranks, O(n log n)
- Counts that double: worked numbers
- The code in five languages
- Variants: the nest itself, turned boxes, touching sides and three sides
- Edge cases and common bugs
- Exercise: nest the most gift boxes
- Nest the most gift boxes in O(n log n)
- A practice set with sample answers
- Check yourself
- Interview questions
- What is the difference between a chain of pairs and a nest of boxes, and why does one need a longest increasing subsequence while the other does not?
- Your solution sorts the boxes by width and runs a longest strictly increasing subsequence on their heights. It passes the samples but fails a hidden test. What input breaks it, and what is the smallest change that fixes it without a second pass?
- Boxes now have a width, a height and a depth, and one nests in another only when all three sides are strictly smaller. With up to 5,000 boxes, what do you code, and what does it cost? What would you do for 10⁵ boxes?
- A leaderboard has 10⁵ rounds. Count the strictly rising runs of rounds that have the greatest length, modulo 10⁹ + 7. Your O(n²) table of lengths and counts times out. What do you store instead, and how do equal scores and the modulus change the code?
- Key takeaways
7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 26 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- bisect: array bisection algorithm (The Python Standard Library) (Python Software Foundation)
- Sorting techniques: key functions and sort stability (Python documentation) (Python Software Foundation)
- A new data structure for cumulative frequency tables (P. M. Fenwick, Software: Practice and Experience 24(3), 1994) (Wiley)
- On computing the length of longest increasing subsequences (M. L. Fredman, Discrete Mathematics 11(1), 1975) (Elsevier)
- Integer.compare (Java SE 25 API documentation) (Oracle)
- The Java Language Specification, Java SE 25: 15.18.2 Additive operators (+ and -) for numeric types (Oracle)
- std::sort (cppreference.com) (cppreference.com)
- Package sort: Slice and SearchInts (Go standard library) (The Go Authors)
- JEP 330: Launch Single-File Source-Code Programs (OpenJDK)
- UndefinedBehaviorSanitizer (Clang documentation) (The LLVM Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress