↓Skip to main content
  1. LeetCode/

LeetCode 3816: Lexicographically Smallest String After Deleting Duplicate Characters

·3 mins· ·
LeetCode Hard 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: hard First Attempt: 2026-01-18

  • Submissions: Wrong Answer ×5 (01-18) → RE, WA, RE, WA → Accepted → Accepted (07-27)

Approach
#

You may delete any copy of a letter that currently appears at least twice; make the string lexicographically smallest. It resembles LC 316, except each letter must keep at least one copy, not exactly one.

Let cnt[ch] be how many ch the current string still has (on the stack plus unprocessed). Left to right:

  1. On c, while the top is larger than c and cnt[top] > 1 (another copy exists), delete it and decrement cnt.
  2. Push c.

One more step at the end: if the last letter still has another copy, delete it too, because a prefix of a string is always lexicographically smaller, e.g. "ab" < "abb".

My Attempts
#

January: deleting right to left, five WAs
#

I first scanned right to left, recording indices to delete and rebuilding the string at the end. The logic never captured “a larger letter before a smaller one should go”, giving five WAs.

July: a monotonic stack tracking too much
#

I switched to a monotonic stack while maintaining two counters, count (unprocessed) and seen (on the stack). Two Runtime Errors came from reading stack[-1] after pop, in the wrong order; two WAs came from trimming the end while checking only the original last character, not the new last character after each deletion.

The final version uses a single cnt, meaning “how many of this letter the current string still has”. Decrementing it on deletion covers copies both on the stack and still ahead.

Solution
#

from collections import Counter

class Solution:
    def lexSmallestAfterDeletion(self, s: str) -> str:
        cnt = Counter(s)
        stack = []
        for ch in s:
            while stack and stack[-1] > ch and cnt[stack[-1]] > 1:
                cnt[stack.pop()] -= 1
            stack.append(ch)
        while cnt[stack[-1]] > 1:                 # trim duplicate letters off the end
            cnt[stack.pop()] -= 1
        return "".join(stack)

Compared with LC 316
#

LC 316LC 3816
Requirementexactly one of each letterat least one of each letter
Deletable whenit appears again latermore than one copy remains
Letter already on the stackskippedpushed anyway
At the endnothingtrim trailing duplicates

Complexity
#

Time O(n), space O(n).

Mistakes to Avoid
#

  • Reading stack[-1] after pop: it is the new top, and the stack may already be empty.
  • Not re-checking the new last letter on each trim step.
  • Reusing the LC 316 approach: this one needs no seen, and needs the extra trimming step at the end.

Interview Explanation
#

cnt tracks how many copies of each letter the current string has. I build the answer left to right with a monotonic stack: while the top is larger than the current letter and has another copy, delete it and decrement cnt. Afterwards, trailing letters with other copies are deleted too, since a prefix is always lexicographically smaller. 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 84: Largest Rectangle in Histogram
·3 mins
LeetCode Daily Hard Array Stack Monotonic-Stack Dynamic-Programming Geometry Histogram
Solving the Largest Rectangle in Histogram problem using monotonic stack approach
LeetCode 2781: Length of the Longest Valid Substring
·3 mins
LeetCode Hard Sliding-Window Hash-Table String Trie
LeetCode 2781 note: forbidden words are at most 10 long, so each right end checks only the last 10 suffixes; on a match push the left end past it, using the shortest match (the rightmost start)
LeetCode 2156: Find Substring With Given Hash Value
·3 mins
LeetCode Hard Sliding-Window Rolling-Hash String
LeetCode 2156 note: the first character has weight p^0, so sliding right to left needs only multiply by p, add the new character, and subtract the outgoing one times p^k; the modulus may not be prime, so no modular inverse
LeetCode 2953: Count Complete Substrings
·3 mins
LeetCode Hard Sliding-Window Hash-Table String
LeetCode 2953 note: split where neighboring letters differ by more than 2; in each segment enumerate the number of distinct letters d, slide a fixed window of d × k, and track how many letters appear exactly k times
LeetCode 2131: Longest Palindrome by Concatenating Two Letter Words
·2 mins
LeetCode Medium Greedy Hash-Table String Counting
LeetCode 2131 note: pair each word with its reverse on both sides; self-palindromes also pair with each other, and only one leftover can sit in the middle