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 27 – Greedy algorithms and proofs

Partition labels: cuts at last occurrences

Cut a string into the most parts with no letter in two of them, using a last-occurrence table and one scan, and see why it equals merging letter ranges.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 7 of 25 minutes free
Free: The problem: one rider per area · The brute force, and why it fails at these sizes · What to notice. With a pass: the other 12 sections and 3 interview answers, about 18 minutes.

What you will learn

  • Partition a string into the most parts so that no letter appears in two parts
  • Use a last-occurrence table to decide where a cut is safe
  • Prove that the scan makes every safe cut and no other
  • Connect the cut rule to merging the first-to-last range of every letter

Before you start

What you’ll learn · Before you start

What you will learn

  • Partition a string into the most parts so that no letter appears in two parts
  • Use a last-occurrence table to decide where a cut is safe
  • Prove that the scan makes every safe cut and no other
  • Connect the cut rule to merging the first-to-last range of every letter

Before you start

On this page

Partition labels asks for a string to be cut into as many pieces as possible so that no letter appears in more than one piece, and for the sizes of the pieces. The greedy answer reads the string twice. First record the last position of every letter; then scan from the left, stretching the current piece to the last position of each letter met in it, and cut the moment the scan reaches the piece’s end. Every cut is safe because each letter seen so far is finished, and no possible cut is missed, so the pieces are as many as they can be: O(n) time and 26 counters of memory. Merging, for every letter, the range from its first to its last position gives the same pieces.

The problem: one rider per area

The problem

A courier’s route for the day is a string with one lowercase letter per stop, the area code of that stop, in the order of delivery. The dispatcher hands the route to several riders as consecutive legs, and each area must be served by one rider only: no letter may appear in two legs. Split the route into as many legs as possible and return their sizes in order. Limits: 1 ≤ n ≤ 10⁶, and only the letters a to z appear.

  1. "mnmopqpqrssr" gives [3, 1, 4, 4]: the legs mnm, o, pqpq and rssr, as in the figure.
  2. "abcd" gives [1, 1, 1, 1]: every stop is an area of its own.
  3. "abca" gives [4]: the first and the last stop share an area, so the route cannot be cut at all.
The route mnmopqpqrssr as twelve boxes, cut after stops 2, 3 and 7 into legs of 3, 1, 4 and 4 stops.The day mnmopqpqrssr, one area per stopm0n1m23o31p4q5p6q74r8s9s10r114Dashed: the three cuts. Legs of 3, 1, 4 and 4 stops.

The first example and its four legs

Text description of the diagram

The figure shows the day's route as twelve boxes, the stops 0 to 11, each holding its area code: m, n, m, o, p, q, p, q, r, s, s, r. Dashed lines mark three cuts, after stops 2, 3 and 7, and brackets under the boxes mark the four legs they make:

  1. stops 0 to 2, "mnm", a leg of 3;
  2. stop 3, "o", a leg of 1;
  3. stops 4 to 7, "pqpq", a leg of 4;
  4. stops 8 to 11, "rssr", a leg of 4.

No letter appears in two legs, and no further cut is possible: any other cut would separate two stops of one area.

The brute force, and why it fails at these sizes

For every place to cut, compare the set of areas on its left with the set on its right, and make every cut where the two share nothing:

Both sides of every cut, with the letters read counted Python · brute/every_cut.py
# A courier's day: one letter per stop, the area code of the stop, in the order of delivery.
DAY = "mnmopqpqrssr"


def legs_by_checking_every_cut(route):
    """Checks every place to cut: a cut is allowed when no area code appears on both sides. Returns (sizes, letters read)."""
    read, cuts = 0, []
    for i in range(len(route) - 1):  # a cut between stop i and stop i + 1
        left, right = set(route[: i + 1]), set(route[i + 1 :])
        read += len(route)
        if not left & right:
            cuts.append(i)
    edges = [-1, *cuts, len(route) - 1]
    return [b - a for a, b in zip(edges, edges[1:])], read


print("the day", DAY, "->", legs_by_checking_every_cut(DAY))
print("abcd ->", legs_by_checking_every_cut("abcd"), "  abca ->", legs_by_checking_every_cut("abca"))
for n in (1_000, 2_000, 4_000):
    route = "".join("abcdefghij"[(i * 7) % 10] for i in range(n))
    print(f"{n:,} stops: {legs_by_checking_every_cut(route)[1]:,} letters read")

Output

the day mnmopqpqrssr -> ([3, 1, 4, 4], 132)
abcd -> ([1, 1, 1, 1], 12)   abca -> ([4], 12)
1,000 stops: 999,000 letters read
2,000 stops: 3,998,000 letters read
4,000 stops: 15,996,000 letters read

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 every_cut.py

Each cut reads the whole route once, so a route of n stops costs about n² letters read: 999,000 for 1,000 stops, 15,996,000 for 4,000 and about 10¹² for a million, which no time limit allows.

What to notice

  • A cut after a stop is allowed exactly when every area seen so far has had its last stop. So the last stop of each area is the only fact about the rest of the route that matters.
  • Allowed cuts never get in each other’s way. Making one does not stop another, so the most legs come from making every allowed cut.

The scan with a last-stop table, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Why every cut is allowed and none is missed: the proof
  • A dry run on the day
  • The same answer as merged spans
  • The code in five languages
    • Checked against every cut on 2,000 random routes
  • Four bugs and the smallest routes that break them
  • Edge cases
  • Variants
  • Exercise: split the route
    • Split a delivery route so no area is served twice
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • In partition labels, why can you make every allowed cut at once, instead of having to choose between allowed cuts?
    • The route now arrives one stop at a time from a live feed, and the dispatcher wants each leg announced as soon as it is certain. When can a leg be announced, what must you store, and what changes if each area's number of stops is known in advance?
    • The area codes are now postal codes of six digits, and a day's file holds 50 million stops with about 20,000 distinct codes. How do you compute the legs within a memory limit of 1 GB, and what do time and memory come to?
    • The rule changes: an area may now be served in several legs, but no leg may visit the same area twice. What is the fewest number of legs, why is your greedy optimal, and how does it differ from partition labels?
  • Key takeaways

1 diagram · 6 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 20 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.