Bracelet from the belt
Pick the longest bracelet pattern you can thread from a moving bead belt without reordering, by testing each candidate with one forward walk.
Beads pass a workbench on a conveyor. You may take a bead or let it go by, but you can never reach back for one that has passed.
The problem
The belt carries coloured beads, written as a string of lowercase letters — r
for red, g for green, b for blue, y for yellow. A bracelet pattern is
another string of the same letters, and you can thread it if its beads appear
along the belt in that order, with any number of unwanted beads skipped
between them.
Given the belt and a catalogue of patterns, return the longest pattern you can thread. If two patterns tie on length, return the one that comes first alphabetically, because that is how the catalogue is filed. If none can be threaded, return the empty string.
Input. belt — a string of bead colours in the order they pass.
patterns — a list of candidate pattern strings.
Output. The chosen pattern, or "".
Example.
belt = "rgbyrgby"
patterns = ["rgby", "rry", "gbb", "ryby"] -> "rgby"
rgby threads from the first four beads. ryby also threads — red at 0, yellow
at 3, blue at 6, yellow at 7 — and is the same length, so the alphabetical
tie-break picks rgby. rry fails: there is only one red before any yellow.
gbb fails: the belt never shows a second blue after the first.
A second example, where the beads are all present but in the wrong order:
belt = "gbr"
patterns = ["rgb"] -> ""
Every colour rgb needs is on the belt, and it still cannot be threaded: the
red passes last.
Constraints.
1 <= len(belt) <= 10^40 <= len(patterns) <= 10^31 <= len(pattern) <= 10^3- Belt and patterns use lowercase letters only.
Hints
Hint 1
Do not try to build patterns out of the belt. Take one candidate at a time and ask a yes-or-no question about it.
Hint 2
Threading is greedy: when the next bead you want appears, take it. Waiting for a later copy of the same colour can never help, and can only cost you beads.
Hint 3
One pointer walks the belt forward and never stops. The other only moves when the bead under it is the one the pattern wants next.
Approach
Brute force
Enumerate every subsequence of the belt and check each against the catalogue.
A belt of length n has 2^n subsequences — for n = 30 that is already a
billion, and the belt can be 10^4 beads long.
The insight
Take the first matching bead you see: the greedy match uses the earliest possible bead for each position, so if greedy fails, nothing succeeds.
Suppose some threading exists. Its first bead sits at or after the first belt position of that colour, so swapping in the earliest copy leaves the rest of the threading still ahead of the pointer. Repeating that bead by bead turns any valid threading into the greedy one, which makes a single forward walk a complete test.
Algorithm
- Keep
best = "". - For each pattern, set a pattern pointer to 0 and walk the belt once.
- Whenever the current bead equals the bead the pattern wants, advance the pattern pointer.
- The pattern threads if its pointer reached the end.
- Replace
bestwhen the pattern is longer, or the same length and alphabetically earlier. - Return
best.
Complexity
Time O(k * n), for k patterns and a belt of n beads — each test is one
forward walk and the pattern pointer never moves backwards. Space O(1) beyond
the answer, since only two indices are held.
Solution
"""Bracelet from the belt — a two-pointer subsequence test per candidate pattern."""
def is_orderable(pattern, belt):
"""True when pattern's beads appear along belt in the same order."""
p = 0
for bead in belt:
if p < len(pattern) and bead == pattern[p]:
p += 1 # invariant: pattern[:p] is matched by the belt so far
return p == len(pattern)
def solve(belt, patterns):
best = ""
for pattern in patterns:
if not is_orderable(pattern, belt):
continue
# Longer wins; on a tie the alphabetically earlier pattern wins.
if len(pattern) > len(best) or (len(pattern) == len(best) and pattern < best):
best = pattern
return bestThe cases that ran
TESTS = [
(("rgbyrgby", ["rgby", "rry", "gbb", "ryby"]), "rgby"),
(("yygbb", ["ygb", "gy", "yb"]), "ygb"),
(("rrr", ["rb", "bb"]), ""),
(("gbr", ["rgb"]), ""),
(("bygbygbyg", []), ""),
(("b", ["b", "bb"]), "b"),
]Pitfalls
- Checking that the letters are merely present. Counting colours accepts
rgbagainst the beltgbrand returns a bracelet that cannot be threaded. - Advancing the belt pointer only on a match. With
belt = "yygbb"and patternygb, the walk sticks on the secondyand never reachesg; the belt pointer has to move on every bead, matched or not. - Keeping the first pattern of maximal length. On the first example that
returns whichever of
rgbyandrybythe catalogue lists first, so the tie-break has to compare strings, not arrival order.
Variants
- Shared sightings — two sequences again, but order carries no information there, so a set replaces the walk.