Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Advanced
  • 40 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25.0.4.1, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 8 of 40 minutes free
Free: Two problems: gift boxes and bookings · The brute force, and why it fails at these sizes · What to notice. With a pass: the other 10 sections and 3 interview answers, about 32 minutes.

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.
Four boxes of rising width and height that nest, and five bookings on a time line with a chain of three picked out.A: four boxes nest(30, 20)in(35, 25)in(40, 35)in(50, 45)B: a chain of three bookings1 to 42 to 35 to 86 to 79 to 100246810hour

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.

Every set of boxes and bookings, with the sets counted Python · brute/brute_force.py
"""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

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.

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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.