Some problems ask you to build the smallest (or largest) sequence by keeping or removing items, where each decision is made against the latest item you kept. A monotonic stack fits well here: the stack is the answer under construction, and a new item can evict worse items from its top. A hint that the answer should come out in ascending or descending order is another strong cue.
The idea#
Removing digits to get the smallest number shows why this greedy choice is safe. Compare two numbers with the same length digit by digit: the first position where they differ decides. So a smaller digit earlier always beats a bigger digit earlier, whatever follows. When the current digit is smaller than the stack's top and you still have removals left, popping the top can only make the result smaller.
The same "is the top still useful?" question also works when you aren't building an ordered result. In a pattern search, such as the 132 pattern below, a new value can make the top useless as a candidate, so you pop it. The stack then holds only candidates that could still matter.
How to spot it#
The decision for each item depends on the latest kept items. Look for these signals.
- "Remove
kdigits / characters to make the smallest (or largest) result". - "Lexicographically smallest" string built by pushing to and popping from a buffer.
- "Does a pattern like
i < j < kwithnums[i] < nums[k] < nums[j]exist?" - You traverse the input and each item's fate is decided against what you kept so far.
Template#
The stack is the result so far. A better item evicts worse tops while the budget allows.
stack = empty
for each item in input:
while budget left and stack and top is worse than item:
pop top # item earlier beats top earlier
spend one unit of budget
push item
if budget left:
remove the leftover from the end # the tail is the worst part
clean up and return stack as the result
Each item is pushed and popped at most once, so the scan is .
To see the template on the digit example, here is the trace for the smallest number after removing k = 3 digits from 1432219. "Worse" means a bigger digit.
| Digit | Pops (k left) |
Stack after |
|---|---|---|
| 1 | 1 |
|
| 4 | 14 |
|
| 3 | 4 (2) | 13 |
| 2 | 3 (1) | 12 |
| 2 | 122 |
|
| 1 | 2 (0) | 121 |
| 9 | 1219 |
Pitfalls#
These slips leave the result too big or plain wrong.
- Forgetting leftover removals. If the input is already in order (like
"12345"), nothing pops. The remaining budget must come off the end, where the worst items are. - Leading zeros. A numeric result may start with zeros after the pops. Strip them, and return
"0"if nothing is left. - Popping without a budget. Each pop spends one removal; check the budget in the
whilecondition. - Comparing with the top only once. Use a
while, not anif. The useful candidate can sit below a useless top.
Problems in this pattern#
These problems use this pattern. Solve them before you open the hints below.
- 402. Remove K Digits
- 2434. Using a Robot to Print the Lexicographically Smallest String
- 456. 132 Pattern
Hints#
Open a hint only after you have tried the problem for a while.
Hint: Remove K Digits
Keep a stack of digits that never decreases from bottom to top. Pop while k and the top is bigger than the new digit. Trim any leftover k from the end, then strip leading zeros and return "0" if nothing is left.
Hint: Using a Robot to Print the Lexicographically Smallest String
Send the top of t to paper when it's no larger than every character still left in s: nothing later could beat it. Precompute the suffix minimum of s, with a sentinel past the end that sorts after z (such as "{"). After each push, pop to paper while top <= suffix_min[i + 1].
Hint: 132 Pattern
Push each value as (value, min_before_it): the value is a candidate j, and the stored minimum is the best i for it. For each new num (a candidate k), pop every top whose value is <= num. If the stack is still non-empty and the top's stored minimum is < num, you found the pattern. Then push (num, running_min).
Hint: 132 Pattern: why popping is safe
If num >= j, any future k that fits under j also fits under num. The stored minimum for num is no larger than the one for j. So num is at least as good a candidate, and j can go.
Key takeaways#
- A smaller item earlier always wins, so evict bigger tops while you're allowed.
- The stack is the answer under construction; its order is the order you want.
- Leftover removals come off the end.
- Store extra context in each entry (like the minimum before it) to test patterns in one pass.
- The core question every time: is the top still useful?