Skip to content
Reliable Data Engineering
Practice problem hard monotonic-stackgreedystrings
Practise with timer, notes and rubric

Smallest Subsequence With Each Letter Once

Pattern: Monotonic Stack · Difficulty: Hard · Asked at: Google, Amazon, ByteDance

Classic version: LeetCode 316 · LeetCode 402

Problem

Remove duplicate letters from s so every distinct letter appears exactly once, and the result is the lexicographically smallest among all such subsequences (relative order from s must be preserved).

Examples

smallest_unique_subsequence("bcabc")    → "abc"
smallest_unique_subsequence("cbacdcbc") → "acdb"

Starter code

def smallest_unique_subsequence(s: str) -> str:
    pass

Hints

Hint 1

Build the answer on a stack. Before pushing c, pop larger letters from the top if they occur again later (you can re-add them then).

Hint 2

Precompute the last index of each letter, and skip letters already in the stack.

Where this shows up in data engineering

Greedy-with-lookahead on a stack is the shape of “produce the smallest/cheapest valid sequence while preserving order”, e.g. choosing which duplicate events to keep so the output is canonical and minimal. It’s also a strong signal problem: you must prove why popping is safe.

Solution

def smallest_unique_subsequence(s: str) -> str:
    last = {c: i for i, c in enumerate(s)}
    stack, in_stack = [], set()
    for i, c in enumerate(s):
        if c in in_stack:
            continue
        # a bigger letter on top can go if it appears again later
        while stack and stack[-1] > c and last[stack[-1]] > i:
            in_stack.discard(stack.pop())
        stack.append(c)
        in_stack.add(c)
    return "".join(stack)

Tests

Your solution should pass these:

assert smallest_unique_subsequence("bcabc") == "abc"
assert smallest_unique_subsequence("cbacdcbc") == "acdb"
assert smallest_unique_subsequence("a") == "a"
assert smallest_unique_subsequence("abacb") == "abc"
assert smallest_unique_subsequence("ecbacba") == "eacb"

Explanation

Greedy argument: if the stack top t is greater than c and t appears later, placing c before t gives a smaller string and loses nothing (we can still place t later). If t never appears again, it must stay.

Skipping letters already placed is safe: the earlier placement is at least as good since everything after it is still free to be arranged.

Complexity: O(n) time (each letter pushed/popped at most once), O(alphabet) space.

Follow-up questions

Remove k digits from a number to make it as small as possible.

Same stack: pop larger digits while k > 0, then trim from the end if k remains, strip leading zeros. O(n).