LeetCode #1047 Easy

Remove All Adjacent Duplicates In String

Remove All Adjacent Duplicates In String is LeetCode 1047 (Easy). A duplicate removal deletes two equal letters that sit next to each other. Keep doing this on s until no such pair is left, and return the final string.

One removal can create a new pair: in "abba", deleting "bb" brings the two as together, and they must be removed too. s holds up to 10^5 lowercase letters.

Constraints
  • 1 <= s.length <= 10^5
  • s consists of lowercase English letters.
stringstack
Open on LeetCode ↗
02

Intuition

Read the letters left to right and keep the ones that have survived so far. A new letter can only pair with the last surviving letter: every earlier survivor already has a different letter right after it.

That is a stack. If the new letter equals the top, pop the top; otherwise push the new letter. Popping exposes the letter underneath, so the next letter is compared with it, and a pair created by a removal is caught right away. Each letter is pushed once and popped at most once, so the whole string is handled in one pass.

How to spot this pattern

This is the cancellation pattern: "delete neighbouring pairs and repeat, where a deletion can create a new pair". Valid Parentheses, Backspace String Compare and Asteroid Collision all compare each new item with the top of a stack in the same way.

03

Approach

Try it first

Before reading on, take "abba" and decide which single letter the last a should be compared with after "bb" is gone. Then pick a structure that always hands you that letter in O(1). Aim for one pass.

1

Start with an empty stack

The stack holds the letters that survive so far, in their original order. A Python list or a C++ string works, since both add and remove at the end in O(1).

2

Compare each letter with the top

For each ch in s, look at the top of the stack. Check that the stack is not empty first; after a full cancellation, or on the first letter, there is no top to read.

3

Pop on a match, push otherwise

If ch equals the top, pop it and drop ch: the two cancel. Otherwise push ch; a later letter may still cancel it.

4

Join the stack

When every letter is read, the stack from bottom to top is the answer. It is already in input order, so join it into a string without reversing.

04

Remove All Adjacent Duplicates In String solution in Python | C++ | Java

▶1class Solution:
▶2 def removeDuplicates(self, s):
▶3 stack = []
▶4 for ch in s:
▶5 if stack and stack[-1] == ch:
▶6 stack.pop()
▶7 else:
▶8 stack.append(ch)
▶9 return "".join(stack)
inputa0b1b2a3c4a5stackemptybottomstack empty, read left to right
s"abbaca"6 letters
The stack holds the letters that survive so far, in order. Only its top can match the next letter, because every letter below the top already has a different letter after it.
inputanextb1b2a3c4a5stackatopbottomstack empty → push 'a'
ch'a'letter 0
stack"a"pushed 'a'
With nothing on the stack there is nothing to cancel against, so 'a' is kept for now.
inputa0bnextb2a3c4a5stackabtopbottom'b' differs from top 'a' → push
ch'b'letter 1
stack"ab"pushed 'b'
'b' is not equal to the top 'a', so no pair forms. Push it; a later letter may still cancel it.
inputa0b1bnexta3c4a5stackatoppopped bbottom'b' matches the top → both removed
ch'b'letter 2
stack"a"popped 'b'
The new 'b' equals the top, so the pair cancels: pop the top and do not push the new one. Now 'a' is on top again, so the next letter is compared with it. This is how a removal that creates a new pair gets caught without rescanning.
inputa0b1b2anextc4a5stackemptypopped abottom'a' matches the top → both removed
ch'a'letter 3
stack""popped 'a'
The new 'a' equals the top, so the pair cancels: pop the top and do not push the new one. The stack is empty again; the next letter has nothing to match.
inputa0b1b2a3cnexta5stackctopbottomstack empty → push 'c'
ch'c'letter 4
stack"c"pushed 'c'
With nothing on the stack there is nothing to cancel against, so 'c' is kept for now.
inputa0b1b2a3c4anextstackcatopbottom'a' differs from top 'c' → push
ch'a'letter 5
stack"ca"pushed 'a'
'a' is not equal to the top 'c', so no pair forms. Push it; a later letter may still cancel it.
inputa0b1b2a3c4a5stackcabottomanswer: "ca"
result"ca"
Read the stack from bottom to top: "ca". It is already in input order, so no reversal is needed, and no two neighbours in it are equal.
05

Common pitfalls

Comparing with the previous input letter

✗ Wrong
for i in range(1, len(s)):
    if s[i] == s[i - 1]:
        ...
✓ Right
for ch in s:
    if stack and stack[-1] == ch:
        stack.pop()
    else:
        stack.append(ch)

The previous letter in s may already be gone. In "abba" the last a has to be compared with the first a, which only becomes its neighbour once "bb" is removed. A single scan of s[i - 1] returns "aa" instead of "".

Deleting from the string and starting over

✗ Wrong
while True:
    for i in range(len(s) - 1):
        if s[i] == s[i + 1]:
            s = s[:i] + s[i + 2:]
            break
    else:
        return s
✓ Right
stack = []
for ch in s:
    if stack and stack[-1] == ch:
        stack.pop()
    else:
        stack.append(ch)
return "".join(stack)

It gives the right answer, but each removal copies the string and restarts the scan. On "ab" * 25000 + "ba" * 25000 that is 50,000 removals over a string of up to 100,000 letters, which is far too slow.

06

Complexity

Time
O(n)
Space
O(n)
n is the length of s. Each letter is pushed once and popped at most once. The stack can hold all n letters when nothing cancels, as in "abc".
07

Adjacent duplicate problems

Three problems remove neighbouring repeats, but they remove different amounts, so the same input can come out differently.

ProblemRemoves"aaa" becomes
1047 Remove All Adjacent Duplicates In String (this page)two equal neighbours at a time"a"
1209 Remove All Adjacent Duplicates in String IIk equal neighbours at a time; the stack stores each letter with its run count"" when k = 3
Recursively Remove Adjacent Duplicatesevery whole run of two or more equal letters""
08

Remove All Adjacent Duplicates In String FAQ

Why does the order of removals not change the answer?

Take any two pairs you could remove right now. If they sit apart, removing one leaves the other in place, so you can do both in either order. If they overlap, as in "aaa", removing either one leaves the same "a". So two different first choices can always be brought back to the same string, and every route ends at the one result the stack builds.

Can it be done without an extra stack?

Yes. Use the string's own array as the stack: keep a write index top, copy each letter to chars[top], and step top back by one instead of popping when the letter matches chars[top - 1]. The answer is chars[:top]. It is the same algorithm with O(1) extra space in languages with mutable strings.