↓Skip to main content
  1. LeetCode/

LeetCode 316: Remove Duplicate Letters

·2 mins· ·
LeetCode Medium Stack Monotonic-Stack Greedy String
Wei Yi Chung
Author
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
Table of Contents

Meta Data
#

Difficulty: medium First Attempt: 2026-07-13

  • Submissions: Accepted → Accepted (retyped 07-27) → Wrong Answer → Accepted
  • Same problem: LC 1081 Smallest Subsequence of Distinct Characters

Approach
#

Keep exactly one of each letter so that the result is lexicographically smallest.

Build the answer left to right. On character c, if the top of the stack is larger than c and appears again later, pop it: the later copy can be used instead, and putting c first is smaller.

Three ingredients:

  1. last[ch]: each letter’s last position, to know whether it appears again.
  2. seen: letters already on the stack. If c is already on the stack, skip it: each letter is kept once, and it already sits in a better position.
  3. A monotonic stack: pop larger letters that can be restored later.

My Attempt: reusing the LC 3816 approach
#

On 07-27 I solved LC 3816 first, then came back to rewrite this one the same way:

cnt = Counter(s)
for ch in s:
    while stack and stack[-1] > ch and cnt[stack[-1]] > 1:
        cnt[stack.pop()] -= 1
    stack.append(ch)

WA. This version allows a letter to appear on the stack several times and only guarantees at least one of each, which is LC 3816’s requirement. 316 wants exactly one, so a seen set must skip letters already on the stack.

Solution
#

class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        last = {ch: i for i, ch in enumerate(s)}
        stack, seen = [], set()
        for i, ch in enumerate(s):
            if ch in seen:
                continue
            while stack and stack[-1] > ch and last[stack[-1]] > i:
                seen.remove(stack.pop())
            stack.append(ch)
            seen.add(ch)
        return "".join(stack)

Complexity
#

Time O(n): each character is pushed and popped at most once. Space O(alphabet size).

Mistakes to Avoid
#

  • No seen: a letter can end up twice.
  • Checking “appears later” with >= instead of >: last[ch] == i means this is the last copy and it cannot be removed.
  • Confusing it with LC 3816: that one is “at least one”, this one is “exactly one”.

Interview Explanation
#

I record each letter’s last position and build the answer left to right with a monotonic stack. Letters already on the stack are skipped; otherwise, while the top is larger than the current letter and appears again later, I pop it, since the later copy can be used. The stack is the answer. Each character enters and leaves once, so time is O(n).

Related

LeetCode 402: Remove K Digits
·2 mins
LeetCode Medium String Stack Greedy Monotonic-Stack
LeetCode 402 note: a monotonically increasing stack; while deletions remain, pop any top larger than the current digit, then strip leading zeros
LeetCode 3816: Lexicographically Smallest String After Deleting Duplicate Characters
·3 mins
LeetCode Hard Stack Monotonic-Stack Greedy String
LeetCode 3816 note: a monotonic stack that deletes a larger top while another copy exists, then trims duplicate letters off the end since a prefix is always smaller
LeetCode 1616: Split Two Strings to Make Palindrome
·3 mins
LeetCode Medium Two-Pointers String Greedy
LeetCode 1616 note: greedily match a’s prefix against b’s suffix from the outside in; the leftover middle must be a palindrome entirely within a or b, and both directions need checking
LeetCode 3952: Maximum Total Value of Covered Indices
·2 mins
LeetCode Medium Dynamic-Programming State-Machine Greedy String
LeetCode 3952 note: each token moves left at most once, so the state is whether this index’s token moved left; moving only helps if the index to the left was not already covered
LeetCode 3555: Smallest Subarray to Sort in Every Sliding Window
·2 mins
LeetCode Medium Two-Pointers Greedy Sorting Array Monotonic-Stack
LeetCode 3555 note: every length-k window is one instance of LeetCode 581; with n only 1000, two O(k) scans per window are enough
LeetCode 581: Shortest Unsorted Continuous Subarray
·2 mins
LeetCode Medium Two-Pointers Greedy Sorting Array Monotonic-Stack
LeetCode 581 note: scanning left to right with a running max, the last index below it is the right boundary; scanning right to left with a running min, the last index above it is the left boundary